@autofleet/rabbit 3.2.21-beta.0 → 3.2.21-beta.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/rabbit",
3
- "version": "3.2.21-beta.0",
3
+ "version": "3.2.21-beta.0.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -19,7 +19,6 @@
19
19
  "amqp-connection-manager": "4.1.9",
20
20
  "amqplib": "0.10.3",
21
21
  "bluebird": "^3.7.2",
22
- "lodash": "^4.17.21",
23
22
  "moment": "^2.29.1",
24
23
  "redis": "^3.1.2",
25
24
  "redis-lock": "^0.1.4"
@@ -31,7 +30,6 @@
31
30
  "@autofleet/logger": "4.0.6",
32
31
  "@types/amqplib": "0.8.2",
33
32
  "@types/jest": "^29.5.12",
34
- "@types/lodash": "^4.17.12",
35
33
  "@types/node": "^20.14.11",
36
34
  "@typescript-eslint/eslint-plugin": "^4.8.1",
37
35
  "eslint": "^7.13.0",
package/src/index.ts CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  import { EventEmitter, once } from 'events';
4
4
  import { promisify } from 'util';
5
- import _ from 'lodash';
6
5
  import moment from 'moment';
7
6
  import RedisLock from 'redis-lock';
8
7
  import {
@@ -36,7 +35,10 @@ import {
36
35
  CustomMessageHeaders,
37
36
  QueuesCache,
38
37
  RedisLockType,
39
- ExchangesCache, CONSUMER_DEFAULT_OPTIONS, QueueSetupPromisesDictionary,
38
+ ExchangesCache,
39
+ CONSUMER_DEFAULT_OPTIONS,
40
+ QueueSetupPromisesDictionary,
41
+ AssertExchangePromisesDictionary,
40
42
  } from './lib/types';
41
43
 
42
44
  // const debug = nodeDebug('af-rabbitmq')
@@ -158,6 +160,8 @@ class RabbitMq implements IAfRabbitMq {
158
160
 
159
161
  queueSetupPromises: QueueSetupPromisesDictionary;
160
162
 
163
+ assertExchangePromises: AssertExchangePromisesDictionary;
164
+
161
165
  options: AfRabbitOptions | undefined;
162
166
 
163
167
  redisClient: any;
@@ -178,6 +182,7 @@ class RabbitMq implements IAfRabbitMq {
178
182
  this.exchanges = {};
179
183
  this.queues = {};
180
184
  this.queueSetupPromises = {};
185
+ this.assertExchangePromises = {};
181
186
  this.consumers = [];
182
187
  this.options = options;
183
188
  this.redisClient = redisConfig && getRedisInstance(redisConfig);
@@ -336,6 +341,7 @@ class RabbitMq implements IAfRabbitMq {
336
341
  });
337
342
 
338
343
  this.connection.on('disconnect', ({ err }) => {
344
+ // this.channel = null;
339
345
  this.consumersTags = [];
340
346
  debug('rabbit: connection closed');
341
347
  if (this.options?.disableReconnect) {
@@ -345,6 +351,7 @@ class RabbitMq implements IAfRabbitMq {
345
351
  logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
346
352
  }
347
353
  });
354
+
348
355
  this.connection.once('connect', async () => {
349
356
  debug('rabbit: connection established');
350
357
  this.creatingConnection = false;
@@ -402,12 +409,19 @@ class RabbitMq implements IAfRabbitMq {
402
409
 
403
410
  async assertExchange(exchangeName: string, options?: any) {
404
411
  const channel: ChannelWrapper = await this.assertChannel();
412
+
405
413
  if (this.exchanges[exchangeName]) {
414
+ delete this.assertExchangePromises[exchangeName];
406
415
  return this.exchanges[exchangeName];
407
416
  }
408
- const exchange = await assertExchangeFanout(channel, exchangeName);
409
- this.exchanges[exchangeName] = exchange;
410
- return exchange;
417
+
418
+ if (this.assertExchangePromises[exchangeName]) {
419
+ return this.assertExchangePromises[exchangeName];
420
+ }
421
+
422
+ this.assertExchangePromises[exchangeName] = assertExchangeFanout(channel, exchangeName);
423
+ this.exchanges[exchangeName] = await this.assertExchangePromises[exchangeName];
424
+ return this.exchanges[exchangeName];
411
425
  }
412
426
 
413
427
  async getQueueLength(queue: string) {
@@ -532,7 +546,6 @@ class RabbitMq implements IAfRabbitMq {
532
546
  return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
533
547
  await confirmChannel.assertQueue(queue, options ? { messageTtl: options.messageTtl } : undefined);
534
548
  await confirmChannel.prefetch(limit, true);
535
- const channelConsumerOptions = optionsWithDefaults.channelConsumeOptions ? _.merge(CONSUMER_DEFAULT_OPTIONS, optionsWithDefaults.channelConsumeOptions) : CONSUMER_DEFAULT_OPTIONS;
536
549
  const { consumerTag } = await confirmChannel.consume(
537
550
  queue,
538
551
  async (msg: ConsumeMessageOrNull) => {
@@ -604,8 +617,7 @@ class RabbitMq implements IAfRabbitMq {
604
617
  } catch (e) {
605
618
  await localNack(msg);
606
619
  }
607
- },
608
- channelConsumerOptions,
620
+ }, CONSUMER_DEFAULT_OPTIONS,
609
621
  );
610
622
  if (!consumerTag) {
611
623
  logger.error(`rabbit: failed to consume from queue ${queue}`);
@@ -685,6 +697,63 @@ class RabbitMq implements IAfRabbitMq {
685
697
  }
686
698
  }
687
699
 
700
+
701
+ async sendToCeleryQueue(
702
+ queue: string,
703
+ taskName: string,
704
+ data: any,
705
+ options?: any,
706
+ customHeaders?: any,
707
+ ): Promise<boolean | undefined> {
708
+ try {
709
+ await this.assertChannel();
710
+ } catch (e) {
711
+ logger.error(`rabbit sendToCeleryQueue: failed to assert channel when sending to queue ${queue}`, { e });
712
+ throw e;
713
+ }
714
+
715
+ try {
716
+ RabbitMq.validateName('queue', queue);
717
+ await this.assertQueue(queue, options);
718
+ } catch (e) {
719
+ logger.error(`rabbit sendToCeleryQueue: failed to assert queue ${queue}`, { e });
720
+ throw e;
721
+ }
722
+
723
+ try {
724
+ // Create Celery-compatible message format
725
+ const celeryMessage = {
726
+ task: taskName,
727
+ id: randomUUID(),
728
+ args: [data],
729
+ };
730
+
731
+ // Merge default Celery properties with custom headers
732
+ const celeryHeaders = {
733
+ ...RabbitMq.getPublishOptions(customHeaders),
734
+ properties: {
735
+ ...RabbitMq.getPublishOptions(customHeaders)?.properties,
736
+ delivery_mode: 2,
737
+ content_type: 'application/json',
738
+ content_encoding: 'utf-8',
739
+ },
740
+ };
741
+
742
+ const res = await this.channel?.sendToQueue(
743
+ queue,
744
+ Buffer.from(JSON.stringify(celeryMessage)),
745
+ celeryHeaders
746
+ );
747
+
748
+ debug(`rabbit: sending to celery queue ${queue}`, { res });
749
+ return res;
750
+ } catch (e) {
751
+ const isConnected = await this.isConnected();
752
+ logger.error(`rabbit sendToCeleryQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
753
+ throw e;
754
+ }
755
+ }
756
+
688
757
  async isConnected() : Promise<boolean> {
689
758
  const connection = await this.getConnection();
690
759
  const isConnected = connection.isConnected();
package/src/lib/types.ts CHANGED
@@ -12,6 +12,10 @@ export interface QueueSetupPromisesDictionary {
12
12
  [key: string]: Promise<Replies.AssertQueue> | undefined
13
13
  }
14
14
 
15
+ export interface AssertExchangePromisesDictionary {
16
+ [key: string]: Promise<Replies.AssertExchange> | undefined
17
+ }
18
+
15
19
  export type CustomMessageHeaders = {
16
20
  redisTimestampValidationKey?: string;
17
21
  }
@@ -27,7 +31,6 @@ export interface ConsumeOptions {
27
31
  useConsumeWithLock?: boolean;
28
32
  auditContext?: any;
29
33
  enableRabbitTrace?: boolean;
30
- channelConsumeOptions?: Options.Consume;
31
34
  }
32
35
 
33
36
  export type CallbackFunction = (msg: ConsumeMessage, ack: any, nack: any) => Promise<any>
package/src/lib/utils.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  import { ChannelWrapper } from 'amqp-connection-manager';
2
- import { ConfirmChannel } from 'amqplib';
2
+ import { ConfirmChannel, Replies } from 'amqplib';
3
+
4
+ export const assertExchangeFanout = async (
5
+ c: ChannelWrapper | ConfirmChannel,
6
+ exchangeName: string,
7
+ ): Promise<Replies.AssertExchange> => c.assertExchange(exchangeName, 'fanout');
3
8
 
4
- export const assertExchangeFanout = async (c: ChannelWrapper | ConfirmChannel, exchangeName: string) => c.assertExchange(exchangeName, 'fanout');
5
9
  export const wrapSetImmediate = (callback: () => any) => new Promise<any>((resolve, reject) => {
6
10
  setImmediate(async () => {
7
11
  try {
package/dist/index.d.ts DELETED
@@ -1,99 +0,0 @@
1
- /// <reference types="node" />
2
- import { EventEmitter } from 'events';
3
- import { AmqpConnectionManager, ChannelWrapper, CreateChannelOpts } from 'amqp-connection-manager';
4
- import { ConfirmChannel, ConsumeMessage, Options, Replies } from 'amqplib';
5
- import { RedisConfig } from './lib/redis';
6
- import { CallbackFunction, ConsumeMessageOrNull, ConsumeOptions, CustomMessageHeaders, QueuesCache, RedisLockType, ExchangesCache, QueueSetupPromisesDictionary } from './lib/types';
7
- export interface IAfRabbitMq {
8
- ack: any;
9
- nack: any;
10
- assertChannel: any;
11
- assertExchange: any;
12
- assertQueue: any;
13
- consume: any;
14
- consumeFromExchange: any;
15
- publish: any;
16
- sendToQueue: any;
17
- redisClient?: any;
18
- }
19
- interface NackOptions {
20
- skipRetry?: boolean;
21
- }
22
- export interface AfRabbitOptions {
23
- disableReconnect?: boolean;
24
- /**
25
- * When you want your own grace-full shutdown, set this to true.
26
- * @default false
27
- */
28
- dontGracefulShutdown?: boolean;
29
- /**
30
- * dont retry on creation error
31
- * @default false
32
- */
33
- dontRetryAssert?: boolean;
34
- rabbitHost?: string;
35
- }
36
- type newChannelOpts = {
37
- name?: string;
38
- onClose?: null | ((args: any | null) => void);
39
- options?: CreateChannelOpts | undefined;
40
- };
41
- type assertChannelOpts = {
42
- channelName?: string;
43
- force?: boolean;
44
- };
45
- declare class RabbitMq implements IAfRabbitMq {
46
- static parseMsg(msg: any): any;
47
- static validateName(type: string, name: string): void;
48
- static getPublishOptions(customHeaders?: CustomMessageHeaders): {
49
- timestamp: number;
50
- timeout: number;
51
- headers: {
52
- "x-af-user-id": any;
53
- "x-trace-id": any;
54
- redisTimestampValidationKey?: string | undefined;
55
- creationTimestamp: number;
56
- };
57
- };
58
- DISCONNECT_MSG: string;
59
- RECONNECT_MSG: string;
60
- channel: ChannelWrapper | null;
61
- publishChannelSetupPromise: Promise<ChannelWrapper> | null;
62
- blockReconnect: boolean | null | undefined;
63
- connection: AmqpConnectionManager | null | undefined;
64
- em: EventEmitter;
65
- creatingConnection: boolean;
66
- exchanges: ExchangesCache;
67
- queues: QueuesCache;
68
- queueSetupPromises: QueueSetupPromisesDictionary;
69
- options: AfRabbitOptions | undefined;
70
- redisClient: any;
71
- redisLock?: RedisLockType;
72
- /** Array of consumers tags used for canceling consumption */
73
- consumersTags: Array<[ConfirmChannel, string]>;
74
- private consumers;
75
- constructor(options?: AfRabbitOptions, redisConfig?: RedisConfig);
76
- private shouldConsumeMessageByTimestamp;
77
- ack: (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp?: boolean, releaseLock?: null) => (userMsg: ConsumeMessage) => Promise<any>;
78
- nack: (channel: ConfirmChannel, queue: string, options: any, deadQueueOptions: Options.AssertQueue, msg: ConsumeMessageOrNull, releaseLock: any) => (userMsg: ConsumeMessageOrNull, { skipRetry, }?: NackOptions) => Promise<any>;
79
- getConnection(): Promise<AmqpConnectionManager>;
80
- getNewChannel({ name, onClose, options }?: newChannelOpts): Promise<ChannelWrapper>;
81
- assertChannel({ force }?: assertChannelOpts): Promise<ChannelWrapper>;
82
- assertExchange(exchangeName: string, options?: any): Promise<any>;
83
- getQueueLength(queue: string): Promise<Replies.AssertQueue>;
84
- private deleteQueue;
85
- bindQueue(queue: string, exchange: string): Promise<void>;
86
- setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue>;
87
- assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any>;
88
- private saveConsumer;
89
- consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any>;
90
- private lockRedisIfNeeded;
91
- private unlockRedisIfNeeded;
92
- private consumeFromRabbit;
93
- consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any>;
94
- publish(exchange: string, content: any, customHeaders?: any): Promise<boolean>;
95
- sendToQueue(queue: string, content: any, options?: any, customHeaders?: any): Promise<boolean | undefined>;
96
- isConnected(): Promise<boolean>;
97
- gracefulShutdown(signal: string): Promise<void>;
98
- }
99
- export default RabbitMq;
package/dist/index.js DELETED
@@ -1,541 +0,0 @@
1
- "use strict";
2
- /* eslint-disable no-empty,consistent-return,no-async-promise-executor,@typescript-eslint/no-unused-vars */
3
- var __importDefault = (this && this.__importDefault) || function (mod) {
4
- return (mod && mod.__esModule) ? mod : { "default": mod };
5
- };
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- const events_1 = require("events");
8
- const util_1 = require("util");
9
- const lodash_1 = __importDefault(require("lodash"));
10
- const moment_1 = __importDefault(require("moment"));
11
- const redis_lock_1 = __importDefault(require("redis-lock"));
12
- const amqp_connection_manager_1 = require("amqp-connection-manager");
13
- const zehut_1 = require("@autofleet/zehut");
14
- const node_crypto_1 = require("node:crypto");
15
- const logger_1 = __importDefault(require("./logger"));
16
- const rabbitError_1 = __importDefault(require("./lib/rabbitError"));
17
- const redis_1 = __importDefault(require("./lib/redis"));
18
- const utils_1 = require("./lib/utils");
19
- const consts_1 = require("./lib/consts");
20
- const types_1 = require("./lib/types");
21
- // const debug = nodeDebug('af-rabbitmq')
22
- const debug = logger_1.default.debug.bind(logger_1.default);
23
- const PUBLISH_TIMEOUT = 1000 * 10;
24
- const HEARTBEAT = '60';
25
- class RabbitMq {
26
- static parseMsg(msg) {
27
- let { content } = msg;
28
- content = content.toString();
29
- try {
30
- content = JSON.parse(content);
31
- }
32
- catch (e) { }
33
- return {
34
- ...msg,
35
- content,
36
- };
37
- }
38
- static validateName(type, name) {
39
- if (!name || name === '') {
40
- throw new rabbitError_1.default(`error while using ${type} with no name`);
41
- }
42
- }
43
- static getPublishOptions(customHeaders = {}) {
44
- const trace = (0, zehut_1.getCurrentPayload)();
45
- const user = trace?.context?.get(consts_1.USER_OBJECT);
46
- const traceId = trace?.context?.get(consts_1.TRACING_HEADER);
47
- const outbreakTrace = zehut_1.outbreak.getCurrentContext();
48
- return {
49
- timestamp: (0, moment_1.default)().unix(),
50
- timeout: PUBLISH_TIMEOUT,
51
- headers: {
52
- creationTimestamp: (0, moment_1.default)().valueOf(),
53
- ...customHeaders,
54
- [consts_1.USER_TRACING_HEADER]: user?.id,
55
- [consts_1.TRACING_HEADER]: traceId || outbreakTrace?.context?.get(consts_1.TRACING_HEADER),
56
- },
57
- };
58
- }
59
- constructor(options, redisConfig) {
60
- this.DISCONNECT_MSG = 'rabbit: connection disconnect';
61
- this.RECONNECT_MSG = 'rabbit: connection disconnect - reconnecting';
62
- this.consumers = [];
63
- this.shouldConsumeMessageByTimestamp = async (msg) => {
64
- if (msg) {
65
- const { properties: { headers } } = msg;
66
- const timestamp = headers?.creationTimestamp;
67
- if (timestamp && headers?.redisTimestampValidationKey && this.redisClient) {
68
- const lastMessageTimestamp = await this.redisClient.getAsync(headers.redisTimestampValidationKey);
69
- return !lastMessageTimestamp || (parseInt(lastMessageTimestamp, 10) <= parseInt(timestamp, 10));
70
- }
71
- return true;
72
- }
73
- return false;
74
- };
75
- this.ack = (channel, msg, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg) => {
76
- if (msg) {
77
- debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
78
- await channel.ack(msg);
79
- const { properties: { headers } } = msg;
80
- const timestamp = headers?.creationTimestamp;
81
- if (shouldUpdateRedisTimestamp && timestamp && headers?.redisTimestampValidationKey && this.redisClient) {
82
- const parsedTimestamp = parseInt(timestamp, 10);
83
- await this.redisClient.setAsync(headers.redisTimestampValidationKey, parsedTimestamp, 'EX', 3600);
84
- await this.unlockRedisIfNeeded(releaseLock);
85
- }
86
- }
87
- };
88
- this.nack = (channel, queue, options, deadQueueOptions, msg, releaseLock) => async (userMsg, { skipRetry = false, } = {}) => {
89
- await this.unlockRedisIfNeeded(releaseLock);
90
- if (channel && msg) {
91
- if (!skipRetry
92
- && (!msg.properties.headers[consts_1.RETRY_HEADER]
93
- || parseInt(msg.properties.headers[consts_1.RETRY_HEADER], 10) < options.retries)) {
94
- await this.sendToQueue(queue, RabbitMq.parseMsg(msg).content, options, {
95
- ...msg.properties.headers,
96
- [consts_1.RETRY_HEADER]: msg.properties.headers[consts_1.RETRY_HEADER]
97
- ? msg.properties.headers[consts_1.RETRY_HEADER] + 1
98
- : 1,
99
- });
100
- }
101
- else {
102
- const deadQueue = `${queue}-dead`;
103
- await this.sendToQueue(deadQueue, RabbitMq.parseMsg(msg).content, deadQueueOptions, {
104
- ...msg.properties.headers,
105
- [consts_1.RETRY_HEADER]: msg.properties.headers[consts_1.RETRY_HEADER]
106
- ? msg.properties.headers[consts_1.RETRY_HEADER] + 1
107
- : 1,
108
- });
109
- }
110
- debug('rabbit nacking message', { deliveryTag: msg.fields.deliveryTag });
111
- await channel.ack(msg);
112
- }
113
- else {
114
- logger_1.default.error('no channel or msg', {
115
- msg,
116
- });
117
- }
118
- };
119
- this.em = new events_1.EventEmitter();
120
- this.channel = null;
121
- this.publishChannelSetupPromise = null;
122
- this.connection = null;
123
- this.creatingConnection = false;
124
- this.exchanges = {};
125
- this.queues = {};
126
- this.queueSetupPromises = {};
127
- this.consumers = [];
128
- this.options = options;
129
- this.redisClient = redisConfig && (0, redis_1.default)(redisConfig);
130
- if (this.redisClient) {
131
- this.redisLock = (0, util_1.promisify)((0, redis_lock_1.default)(this.redisClient));
132
- }
133
- this.consumersTags = [];
134
- logger_1.default.info(`rabbit: [gracefully-shutdown] adding gracefully shutdown for process.pid ${process.pid}`);
135
- if (!this.options?.dontGracefulShutdown) {
136
- process.on('SIGTERM', async () => {
137
- await this.gracefulShutdown('SIGTERM');
138
- });
139
- process.on('SIGINT', async () => {
140
- await this.gracefulShutdown('SIGINT');
141
- });
142
- }
143
- }
144
- async getConnection() {
145
- return new Promise(async (resolve, reject) => {
146
- if (this.blockReconnect) {
147
- debug('rabbit: block reconnect');
148
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
149
- // @ts-ignore
150
- return resolve();
151
- }
152
- if (this.connection !== null) {
153
- if (this.options?.disableReconnect || this.connection?.isConnected()) {
154
- debug('rabbit: connection - is connected');
155
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
156
- // @ts-ignore
157
- return resolve(this.connection);
158
- }
159
- debug('rabbit: connection - reconnecting');
160
- }
161
- if (this.creatingConnection) {
162
- debug('rabbit: creating connection emi');
163
- this.em.once(consts_1.CONNECTION_CREATED_CONST, resolve);
164
- this.em.once(consts_1.CONNECTION_FAILED_CONST, reject);
165
- return;
166
- }
167
- this.creatingConnection = true;
168
- let isResolved = false;
169
- // It is import to use it as a function and not as a variable
170
- // because of k8s changes the env variables
171
- // and we want to use the new values
172
- const findServers = () => {
173
- const userName = process.env.RABBITMQ_USERNAME || 'guest';
174
- const password = process.env.RABBITMQ_PASSWORD || 'guest';
175
- const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
176
- debug('rabbit: creating connection', { host, userName, HEARTBEAT });
177
- return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
178
- };
179
- const defaultUrls = findServers();
180
- const connection = await (0, amqp_connection_manager_1.connect)(defaultUrls, {
181
- findServers,
182
- });
183
- this.connection = connection;
184
- this.connection.on('error', (err) => {
185
- logger_1.default.error('rabbit: connection error', { err });
186
- if (!isResolved) {
187
- isResolved = true;
188
- reject(err);
189
- this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
190
- }
191
- });
192
- this.connection.on('connectFailed', (err) => {
193
- this.consumersTags = [];
194
- logger_1.default.error('rabbit: connection connectFailed', { err });
195
- if (!isResolved) {
196
- isResolved = true;
197
- reject(err);
198
- this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
199
- }
200
- });
201
- this.connection.on('disconnect', ({ err }) => {
202
- this.consumersTags = [];
203
- debug('rabbit: connection closed');
204
- if (this.options?.disableReconnect) {
205
- logger_1.default.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
206
- this.blockReconnect = true;
207
- }
208
- else {
209
- logger_1.default.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
210
- }
211
- });
212
- this.connection.once('connect', async () => {
213
- debug('rabbit: connection established');
214
- this.creatingConnection = false;
215
- this.em.emit(consts_1.CONNECTION_CREATED_CONST, connection);
216
- isResolved = true;
217
- resolve(connection);
218
- });
219
- });
220
- }
221
- async getNewChannel({ name = (0, utils_1.rand)().toString(), onClose = null, options = {} } = {}) {
222
- let connection;
223
- try {
224
- connection = await this.getConnection();
225
- }
226
- catch (e) {
227
- logger_1.default.error(`rabbit: error on get connection for new channel ${name} `, { e });
228
- throw e;
229
- }
230
- const channel = connection.createChannel({ ...options });
231
- (0, events_1.once)(channel, 'close').then((args) => {
232
- logger_1.default.error(`rabbit: channel ${name} closed`);
233
- onClose?.(args);
234
- });
235
- try {
236
- await (0, events_1.once)(channel, 'connect');
237
- debug(`rabbit: channel ${name} CONNECTED`);
238
- return channel;
239
- }
240
- catch (err) {
241
- logger_1.default.error(`rabbit: channel error ${name} error`, { err });
242
- throw err;
243
- }
244
- }
245
- async assertChannel({ force = false } = {}) {
246
- if (!this.publishChannelSetupPromise) {
247
- this.publishChannelSetupPromise = new Promise(async (resolve, reject) => {
248
- if (this.channel && !force) {
249
- return resolve(this.channel);
250
- }
251
- try {
252
- const channel = await this.getNewChannel({});
253
- channel.on('error', (err) => {
254
- logger_1.default.error('rabbit: channel error', { err });
255
- });
256
- this.channel = channel;
257
- resolve(channel);
258
- }
259
- catch (e) {
260
- reject(e);
261
- }
262
- });
263
- }
264
- return this.publishChannelSetupPromise;
265
- }
266
- async assertExchange(exchangeName, options) {
267
- const channel = await this.assertChannel();
268
- if (this.exchanges[exchangeName]) {
269
- return this.exchanges[exchangeName];
270
- }
271
- const exchange = await (0, utils_1.assertExchangeFanout)(channel, exchangeName);
272
- this.exchanges[exchangeName] = exchange;
273
- return exchange;
274
- }
275
- async getQueueLength(queue) {
276
- RabbitMq.validateName('queue', queue);
277
- const { channel } = this;
278
- if (!channel) {
279
- throw new Error('channel is not defined');
280
- }
281
- debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
282
- return channel?.checkQueue(queue);
283
- }
284
- async deleteQueue(queue) {
285
- RabbitMq.validateName('queue', queue);
286
- const channel = await this.assertChannel();
287
- logger_1.default.info('rabbit: deleting queue', { queue });
288
- const deleteQueueRes = await channel.deleteQueue(queue);
289
- debug('queue deleted', deleteQueueRes);
290
- return deleteQueueRes;
291
- }
292
- async bindQueue(queue, exchange) {
293
- const channel = await this.assertChannel();
294
- await channel.addSetup((setupChannel) => setupChannel.bindQueue(queue, exchange, ''));
295
- return channel.bindQueue(queue, exchange, '');
296
- }
297
- async setupQueue(queueName, options) {
298
- let queue;
299
- try {
300
- const channel = await this.assertChannel();
301
- debug('assertQueue->channel.addSetup', { queueName });
302
- await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, options));
303
- debug('assertQueue->channel.assertQueue', { queueName });
304
- queue = await channel.assertQueue(queueName, options);
305
- }
306
- catch (e) {
307
- logger_1.default.error('rabbit: assertQueue error', { queueName, options, error: e });
308
- if (!this.options?.dontRetryAssert) {
309
- debug('retrying assertQueue', { queueName });
310
- const channel = await this.assertChannel({ force: true });
311
- await this.deleteQueue(queueName);
312
- debug('retrying assertQueue->channel.addSetup', { queueName });
313
- await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, options));
314
- debug('retrying assertQueue->channel.assertQueue', { queueName });
315
- queue = await channel.assertQueue(queueName, options);
316
- }
317
- else {
318
- throw e;
319
- }
320
- }
321
- this.queues[queueName] = queueName;
322
- return queue;
323
- }
324
- async assertQueue(queueName, options) {
325
- RabbitMq.validateName('queue', queueName);
326
- if (this.queues[queueName]) {
327
- delete this.queueSetupPromises[queueName];
328
- return this.queues[queueName];
329
- }
330
- if (this.queueSetupPromises[queueName]) {
331
- return this.queueSetupPromises[queueName];
332
- }
333
- this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
334
- return this.queueSetupPromises[queueName];
335
- }
336
- saveConsumer(queue, callback, options) {
337
- const isConsumerExist = this.consumers.some((consumer) => consumer.queue === queue);
338
- if (!isConsumerExist) {
339
- logger_1.default.info(`rabbit: consumer: ${queue} saved in consumer array`);
340
- this.consumers.push({
341
- queue,
342
- callback,
343
- options,
344
- });
345
- }
346
- }
347
- async consume(queue, callback, options) {
348
- await this.consumeFromRabbit(queue, callback, options);
349
- }
350
- async lockRedisIfNeeded(msg, options) {
351
- const { properties: { headers } } = msg;
352
- const timestamp = headers?.creationTimestamp;
353
- let releaseLock = null;
354
- if (options.useConsumeWithLock && timestamp && headers?.redisTimestampValidationKey && this.redisLock) {
355
- releaseLock = await this.redisLock(headers.redisTimestampValidationKey, options?.lockTimeout || consts_1.DEFAULT_LOCK_TIMEOUT);
356
- }
357
- return releaseLock;
358
- }
359
- async unlockRedisIfNeeded(releaseLock) {
360
- if (this.redisLock && releaseLock) {
361
- await releaseLock();
362
- }
363
- }
364
- async consumeFromRabbit(queue, callback, options) {
365
- const optionsWithDefaults = { ...consts_1.DEFAULT_OPTIONS, ...options };
366
- RabbitMq.validateName('queue', queue);
367
- this.saveConsumer(queue, callback, options);
368
- const uniqueId = (0, node_crypto_1.randomUUID)();
369
- const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace, } = optionsWithDefaults;
370
- if (useConsumeWithLock) {
371
- if (!this.redisLock) {
372
- throw new Error('Usage of consumeWithLock requires RedisInstance');
373
- }
374
- logger_1.default.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
375
- }
376
- const channel = await this.getNewChannel({});
377
- return channel.addSetup(async (confirmChannel) => {
378
- await confirmChannel.assertQueue(queue, options ? { messageTtl: options.messageTtl } : undefined);
379
- await confirmChannel.prefetch(limit, true);
380
- const channelConsumerOptions = optionsWithDefaults.channelConsumeOptions ? lodash_1.default.merge(types_1.CONSUMER_DEFAULT_OPTIONS, optionsWithDefaults.channelConsumeOptions) : types_1.CONSUMER_DEFAULT_OPTIONS;
381
- const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
382
- if (!msg) {
383
- return null;
384
- }
385
- const traceId = msg.properties.headers[consts_1.TRACING_HEADER];
386
- const userId = msg.properties.headers[consts_1.USER_TRACING_HEADER];
387
- const parsedMessage = RabbitMq.parseMsg(msg);
388
- const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
389
- const trace = (0, zehut_1.newTrace)(zehut_1.traceTypes.RABBIT);
390
- // setting also outbreak trace as part of legacy code
391
- const outbreakTrace = zehut_1.outbreak.newTrace(zehut_1.traceTypes.RABBIT);
392
- // enableRabbitTrace is a flag to protect from different flows that doesn't work with permission
393
- // and we don't want to fail the flow because of it
394
- if (userId && enableRabbitTrace) {
395
- try {
396
- await Promise.all([
397
- (0, zehut_1.createOrSetRabbitTrace)(trace, userId),
398
- (0, zehut_1.createOrSetRabbitTrace)(outbreakTrace, userId),
399
- ]);
400
- }
401
- catch (e) {
402
- logger_1.default.error('rabbit: failed to setRabbitTrace', { userId, e });
403
- return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
404
- }
405
- }
406
- if (traceId) {
407
- trace?.context?.set(consts_1.TRACING_HEADER, traceId);
408
- outbreakTrace?.context.set(consts_1.TRACING_HEADER, traceId);
409
- }
410
- if (auditContext) {
411
- await auditContext(queue);
412
- }
413
- const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
414
- if (!shouldConsume) {
415
- await this.unlockRedisIfNeeded(releaseLock);
416
- return this.ack(confirmChannel, msg)(msg);
417
- }
418
- let messageAcked = false;
419
- // setting the localAck function to be used in the callback
420
- const localAck = async () => {
421
- if (messageAcked) {
422
- return;
423
- }
424
- messageAcked = true;
425
- return this.ack(confirmChannel, msg, true, releaseLock)(msg);
426
- };
427
- const localNack = async (_, nackOptions = {}) => {
428
- if (messageAcked) {
429
- return;
430
- }
431
- debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
432
- messageAcked = true;
433
- return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
434
- };
435
- try {
436
- await callback(parsedMessage, localAck, localNack);
437
- }
438
- catch (e) {
439
- await localNack(msg);
440
- }
441
- }, channelConsumerOptions);
442
- if (!consumerTag) {
443
- logger_1.default.error(`rabbit: failed to consume from queue ${queue}`);
444
- }
445
- else {
446
- logger_1.default.info(`rabbit: adding tag ${consumerTag} to the array.`);
447
- this.consumersTags.push([confirmChannel, consumerTag]);
448
- }
449
- });
450
- }
451
- async consumeFromExchange(queue, exchange, callback, options) {
452
- const optionsWithDefaults = { ...consts_1.DEFAULT_OPTIONS, ...options };
453
- RabbitMq.validateName('exchange', exchange);
454
- RabbitMq.validateName('queue', queue);
455
- const { limit, deadMessageTtl } = optionsWithDefaults;
456
- await this.saveConsumer(queue, callback, options);
457
- const channel = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
458
- return channel.addSetup(async (c) => {
459
- const assertExchange = await (0, utils_1.assertExchangeFanout)(c, exchange);
460
- await c.assertQueue(queue);
461
- this.exchanges[exchange] = assertExchange;
462
- await c.prefetch(limit, true);
463
- return Promise.all([
464
- c.bindQueue(queue, exchange, ''),
465
- this.consume(queue, callback, options),
466
- ]);
467
- });
468
- }
469
- async publish(exchange, content, customHeaders) {
470
- return (0, utils_1.wrapSetImmediate)(async () => {
471
- RabbitMq.validateName('exchange', exchange);
472
- const channel = await this.assertChannel();
473
- await this.assertExchange(exchange);
474
- await channel.publish(exchange, '', Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
475
- });
476
- }
477
- async sendToQueue(queue, content, options, customHeaders) {
478
- try {
479
- await this.assertChannel();
480
- }
481
- catch (e) {
482
- logger_1.default.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
483
- throw e;
484
- }
485
- try {
486
- RabbitMq.validateName('queue', queue);
487
- await this.assertQueue(queue, options);
488
- }
489
- catch (e) {
490
- logger_1.default.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
491
- throw e;
492
- }
493
- try {
494
- const res = await this.channel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
495
- debug(`rabbit: sending to queue ${queue}`, { res });
496
- return res;
497
- }
498
- catch (e) {
499
- const isConnected = await this.isConnected();
500
- logger_1.default.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
501
- throw e;
502
- }
503
- }
504
- async isConnected() {
505
- const connection = await this.getConnection();
506
- const isConnected = connection.isConnected();
507
- if (!isConnected) {
508
- logger_1.default.error('rabbit: isConnected - false');
509
- return false;
510
- }
511
- const channel = await this.assertChannel();
512
- try {
513
- await Promise.all([
514
- channel.waitForConnect(),
515
- ...this.consumers.map((c) => channel.checkQueue(c.queue)),
516
- ]);
517
- }
518
- catch (e) {
519
- logger_1.default.error('rabbit: isConnected - false');
520
- return false;
521
- }
522
- logger_1.default.info('rabbit: isConnected - true');
523
- return true;
524
- }
525
- async gracefulShutdown(signal) {
526
- const tagsNumber = this.consumersTags.length;
527
- logger_1.default.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
528
- const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
529
- // Clean the array to avoid race
530
- this.consumersTags = [];
531
- const results = await Promise.allSettled(cancelTagPromises);
532
- const rejected = results.filter((p) => p.status === 'rejected');
533
- if (rejected.length > 0) {
534
- logger_1.default.warn(`rabbit: [gracefully-shutdown] #${rejected.length}/${tagsNumber} tags failed to cancel: ${rejected}`);
535
- }
536
- else {
537
- logger_1.default.info('rabbit: [gracefully-shutdown] all tags successfully canceled.');
538
- }
539
- }
540
- }
541
- exports.default = RabbitMq;
@@ -1,18 +0,0 @@
1
- export declare const DEFAULT_DEAD_TTL_TWO_DAYS: number;
2
- export declare const DEFAULT_LOCK_TIMEOUT: number;
3
- export declare const RETRY_HEADER = "x-retry-count";
4
- export declare const TRACING_HEADER = "x-trace-id";
5
- export declare const USER_TRACING_HEADER = "x-af-user-id";
6
- export declare const USER_OBJECT = "userObject";
7
- export declare const DEFAULT_USE_CONSUME_WITH_LOCK = false;
8
- export declare const CONNECTION_CREATED_CONST = "connectionCreated";
9
- export declare const CONNECTION_FAILED_CONST = "connectionFailed";
10
- export declare const DEFAULT_OPTIONS: {
11
- limit: number;
12
- retries: number;
13
- deadMessageTtl: number;
14
- lockTimeout: number;
15
- useConsumeWithLock: boolean;
16
- auditContext: null;
17
- enableRabbitTrace: boolean;
18
- };
@@ -1,21 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_OPTIONS = exports.CONNECTION_FAILED_CONST = exports.CONNECTION_CREATED_CONST = exports.DEFAULT_USE_CONSUME_WITH_LOCK = exports.USER_OBJECT = exports.USER_TRACING_HEADER = exports.TRACING_HEADER = exports.RETRY_HEADER = exports.DEFAULT_LOCK_TIMEOUT = exports.DEFAULT_DEAD_TTL_TWO_DAYS = void 0;
4
- exports.DEFAULT_DEAD_TTL_TWO_DAYS = 60000 * 60 * 12;
5
- exports.DEFAULT_LOCK_TIMEOUT = 1000 * 5;
6
- exports.RETRY_HEADER = 'x-retry-count';
7
- exports.TRACING_HEADER = 'x-trace-id';
8
- exports.USER_TRACING_HEADER = 'x-af-user-id';
9
- exports.USER_OBJECT = 'userObject';
10
- exports.DEFAULT_USE_CONSUME_WITH_LOCK = false;
11
- exports.CONNECTION_CREATED_CONST = 'connectionCreated';
12
- exports.CONNECTION_FAILED_CONST = 'connectionFailed';
13
- exports.DEFAULT_OPTIONS = {
14
- limit: 1,
15
- retries: 1,
16
- deadMessageTtl: exports.DEFAULT_DEAD_TTL_TWO_DAYS,
17
- lockTimeout: exports.DEFAULT_LOCK_TIMEOUT,
18
- useConsumeWithLock: exports.DEFAULT_USE_CONSUME_WITH_LOCK,
19
- auditContext: null,
20
- enableRabbitTrace: false,
21
- };
@@ -1,3 +0,0 @@
1
- export default class RabbitError extends Error {
2
- constructor(message: string);
3
- }
@@ -1,9 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- class RabbitError extends Error {
4
- constructor(message) {
5
- super(message);
6
- this.name = 'RabbitError';
7
- }
8
- }
9
- exports.default = RabbitError;
@@ -1,7 +0,0 @@
1
- export type RedisConfig = {
2
- host: string;
3
- port: number | undefined;
4
- prefix?: string;
5
- };
6
- declare const getRedisInstance: (config: RedisConfig) => any;
7
- export default getRedisInstance;
package/dist/lib/redis.js DELETED
@@ -1,11 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const bluebird_1 = __importDefault(require("bluebird"));
7
- const redis = require('redis');
8
- bluebird_1.default.promisifyAll(redis.RedisClient.prototype);
9
- bluebird_1.default.promisifyAll(redis.Multi.prototype);
10
- const getRedisInstance = (config) => redis.createClient(config);
11
- exports.default = getRedisInstance;
@@ -1,41 +0,0 @@
1
- import { ConsumeMessage, Options, Replies } from 'amqplib';
2
- export interface ExchangesCache {
3
- [key: string]: any;
4
- }
5
- export interface QueuesCache {
6
- [key: string]: any;
7
- }
8
- export interface QueueSetupPromisesDictionary {
9
- [key: string]: Promise<Replies.AssertQueue> | undefined;
10
- }
11
- export type CustomMessageHeaders = {
12
- redisTimestampValidationKey?: string;
13
- };
14
- export type RedisLockType = (args0?: string, arg1?: number) => Promise<any>;
15
- export type ConsumeMessageOrNull = ConsumeMessage | null;
16
- export interface ConsumeOptions {
17
- retries?: number;
18
- deadMessageTtl?: number;
19
- messageTtl?: number;
20
- limit?: number;
21
- lockTimeout?: number;
22
- useConsumeWithLock?: boolean;
23
- auditContext?: any;
24
- enableRabbitTrace?: boolean;
25
- channelConsumeOptions?: Options.Consume;
26
- }
27
- export type CallbackFunction = (msg: ConsumeMessage, ack: any, nack: any) => Promise<any>;
28
- export type newChannelOpts = {
29
- name?: string;
30
- onClose?: null | ((args: any | null) => void);
31
- };
32
- export type assertChannelOpts = {
33
- channelName?: string;
34
- force?: boolean;
35
- };
36
- export type AfConsumer = {
37
- queue: string;
38
- callback: CallbackFunction;
39
- options: ConsumeOptions | undefined;
40
- };
41
- export declare const CONSUMER_DEFAULT_OPTIONS: Options.Consume;
package/dist/lib/types.js DELETED
@@ -1,11 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CONSUMER_DEFAULT_OPTIONS = void 0;
4
- const HA_PROMOTE_ON_FAILURE = 'ha-promote-on-failure';
5
- const HA_PROMOTE_ON_SHUTDOWN = 'ha-promote-on-shutdown';
6
- exports.CONSUMER_DEFAULT_OPTIONS = {
7
- arguments: {
8
- [HA_PROMOTE_ON_FAILURE]: 'always',
9
- [HA_PROMOTE_ON_SHUTDOWN]: 'always',
10
- },
11
- };
@@ -1,5 +0,0 @@
1
- import { ChannelWrapper } from 'amqp-connection-manager';
2
- import { ConfirmChannel } from 'amqplib';
3
- export declare const assertExchangeFanout: (c: ChannelWrapper | ConfirmChannel, exchangeName: string) => Promise<import("amqplib").Replies.AssertExchange>;
4
- export declare const wrapSetImmediate: (callback: () => any) => Promise<any>;
5
- export declare const rand: () => number;
package/dist/lib/utils.js DELETED
@@ -1,19 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.rand = exports.wrapSetImmediate = exports.assertExchangeFanout = void 0;
4
- const assertExchangeFanout = async (c, exchangeName) => c.assertExchange(exchangeName, 'fanout');
5
- exports.assertExchangeFanout = assertExchangeFanout;
6
- const wrapSetImmediate = (callback) => new Promise((resolve, reject) => {
7
- setImmediate(async () => {
8
- try {
9
- const value = await callback();
10
- resolve(value);
11
- }
12
- catch (error) {
13
- reject(error);
14
- }
15
- });
16
- });
17
- exports.wrapSetImmediate = wrapSetImmediate;
18
- const rand = () => Math.floor(Math.random() * 100000);
19
- exports.rand = rand;
package/dist/logger.d.ts DELETED
@@ -1,2 +0,0 @@
1
- declare const logger: import("@autofleet/logger").LoggerInstanceManager;
2
- export default logger;
package/dist/logger.js DELETED
@@ -1,8 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const logger_1 = __importDefault(require("@autofleet/logger"));
7
- const logger = (0, logger_1.default)();
8
- exports.default = logger;
package/dist/mock.d.ts DELETED
@@ -1,14 +0,0 @@
1
- import 'jest';
2
- import { IAfRabbitMq } from './index';
3
- declare class RabbitMq implements IAfRabbitMq {
4
- ack: any;
5
- nack: any;
6
- assertChannel: any;
7
- assertExchange: any;
8
- assertQueue: any;
9
- consume: any;
10
- consumeFromExchange: any;
11
- publish: any;
12
- sendToQueue: any;
13
- }
14
- export default RabbitMq;
package/dist/mock.js DELETED
@@ -1,18 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- // eslint-disable-next-line import/no-extraneous-dependencies
4
- require("jest");
5
- class RabbitMq {
6
- constructor() {
7
- this.ack = jest.fn();
8
- this.nack = jest.fn();
9
- this.assertChannel = jest.fn();
10
- this.assertExchange = jest.fn();
11
- this.assertQueue = jest.fn();
12
- this.consume = jest.fn();
13
- this.consumeFromExchange = jest.fn();
14
- this.publish = jest.fn();
15
- this.sendToQueue = jest.fn();
16
- }
17
- }
18
- exports.default = RabbitMq;