@autofleet/rabbit 3.2.20 → 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.20",
3
+ "version": "3.2.21-beta.0.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -43,4 +43,4 @@
43
43
  },
44
44
  "author": "",
45
45
  "license": "ISC"
46
- }
46
+ }
package/src/index.ts CHANGED
@@ -697,6 +697,63 @@ class RabbitMq implements IAfRabbitMq {
697
697
  }
698
698
  }
699
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
+
700
757
  async isConnected() : Promise<boolean> {
701
758
  const connection = await this.getConnection();
702
759
  const isConnected = connection.isConnected();
package/dist/index.d.ts DELETED
@@ -1,100 +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, AssertExchangePromisesDictionary } 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
- assertExchangePromises: AssertExchangePromisesDictionary;
70
- options: AfRabbitOptions | undefined;
71
- redisClient: any;
72
- redisLock?: RedisLockType;
73
- /** Array of consumers tags used for canceling consumption */
74
- consumersTags: Array<[ConfirmChannel, string]>;
75
- private consumers;
76
- constructor(options?: AfRabbitOptions, redisConfig?: RedisConfig);
77
- private shouldConsumeMessageByTimestamp;
78
- ack: (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp?: boolean, releaseLock?: null) => (userMsg: ConsumeMessage) => Promise<any>;
79
- nack: (channel: ConfirmChannel, queue: string, options: any, deadQueueOptions: Options.AssertQueue, msg: ConsumeMessageOrNull, releaseLock: any) => (userMsg: ConsumeMessageOrNull, { skipRetry, }?: NackOptions) => Promise<any>;
80
- getConnection(): Promise<AmqpConnectionManager>;
81
- getNewChannel({ name, onClose, options }?: newChannelOpts): Promise<ChannelWrapper>;
82
- assertChannel({ force }?: assertChannelOpts): Promise<ChannelWrapper>;
83
- assertExchange(exchangeName: string, options?: any): Promise<any>;
84
- getQueueLength(queue: string): Promise<Replies.AssertQueue>;
85
- private deleteQueue;
86
- bindQueue(queue: string, exchange: string): Promise<void>;
87
- setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue>;
88
- assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any>;
89
- private saveConsumer;
90
- consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any>;
91
- private lockRedisIfNeeded;
92
- private unlockRedisIfNeeded;
93
- private consumeFromRabbit;
94
- consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any>;
95
- publish(exchange: string, content: any, customHeaders?: any): Promise<boolean>;
96
- sendToQueue(queue: string, content: any, options?: any, customHeaders?: any): Promise<boolean | undefined>;
97
- isConnected(): Promise<boolean>;
98
- gracefulShutdown(signal: string): Promise<void>;
99
- }
100
- export default RabbitMq;
package/dist/index.js DELETED
@@ -1,545 +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 moment_1 = __importDefault(require("moment"));
10
- const redis_lock_1 = __importDefault(require("redis-lock"));
11
- const amqp_connection_manager_1 = require("amqp-connection-manager");
12
- const zehut_1 = require("@autofleet/zehut");
13
- const node_crypto_1 = require("node:crypto");
14
- const logger_1 = __importDefault(require("./logger"));
15
- const rabbitError_1 = __importDefault(require("./lib/rabbitError"));
16
- const redis_1 = __importDefault(require("./lib/redis"));
17
- const utils_1 = require("./lib/utils");
18
- const consts_1 = require("./lib/consts");
19
- const types_1 = require("./lib/types");
20
- // const debug = nodeDebug('af-rabbitmq')
21
- const debug = logger_1.default.debug.bind(logger_1.default);
22
- const PUBLISH_TIMEOUT = 1000 * 10;
23
- const HEARTBEAT = '60';
24
- class RabbitMq {
25
- static parseMsg(msg) {
26
- let { content } = msg;
27
- content = content.toString();
28
- try {
29
- content = JSON.parse(content);
30
- }
31
- catch (e) { }
32
- return {
33
- ...msg,
34
- content,
35
- };
36
- }
37
- static validateName(type, name) {
38
- if (!name || name === '') {
39
- throw new rabbitError_1.default(`error while using ${type} with no name`);
40
- }
41
- }
42
- static getPublishOptions(customHeaders = {}) {
43
- const trace = (0, zehut_1.getCurrentPayload)();
44
- const user = trace?.context?.get(consts_1.USER_OBJECT);
45
- const traceId = trace?.context?.get(consts_1.TRACING_HEADER);
46
- const outbreakTrace = zehut_1.outbreak.getCurrentContext();
47
- return {
48
- timestamp: (0, moment_1.default)().unix(),
49
- timeout: PUBLISH_TIMEOUT,
50
- headers: {
51
- creationTimestamp: (0, moment_1.default)().valueOf(),
52
- ...customHeaders,
53
- [consts_1.USER_TRACING_HEADER]: user?.id,
54
- [consts_1.TRACING_HEADER]: traceId || outbreakTrace?.context?.get(consts_1.TRACING_HEADER),
55
- },
56
- };
57
- }
58
- constructor(options, redisConfig) {
59
- this.DISCONNECT_MSG = 'rabbit: connection disconnect';
60
- this.RECONNECT_MSG = 'rabbit: connection disconnect - reconnecting';
61
- this.consumers = [];
62
- this.shouldConsumeMessageByTimestamp = async (msg) => {
63
- if (msg) {
64
- const { properties: { headers } } = msg;
65
- const timestamp = headers?.creationTimestamp;
66
- if (timestamp && headers?.redisTimestampValidationKey && this.redisClient) {
67
- const lastMessageTimestamp = await this.redisClient.getAsync(headers.redisTimestampValidationKey);
68
- return !lastMessageTimestamp || (parseInt(lastMessageTimestamp, 10) <= parseInt(timestamp, 10));
69
- }
70
- return true;
71
- }
72
- return false;
73
- };
74
- this.ack = (channel, msg, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg) => {
75
- if (msg) {
76
- debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
77
- await channel.ack(msg);
78
- const { properties: { headers } } = msg;
79
- const timestamp = headers?.creationTimestamp;
80
- if (shouldUpdateRedisTimestamp && timestamp && headers?.redisTimestampValidationKey && this.redisClient) {
81
- const parsedTimestamp = parseInt(timestamp, 10);
82
- await this.redisClient.setAsync(headers.redisTimestampValidationKey, parsedTimestamp, 'EX', 3600);
83
- await this.unlockRedisIfNeeded(releaseLock);
84
- }
85
- }
86
- };
87
- this.nack = (channel, queue, options, deadQueueOptions, msg, releaseLock) => async (userMsg, { skipRetry = false, } = {}) => {
88
- await this.unlockRedisIfNeeded(releaseLock);
89
- if (channel && msg) {
90
- if (!skipRetry
91
- && (!msg.properties.headers[consts_1.RETRY_HEADER]
92
- || parseInt(msg.properties.headers[consts_1.RETRY_HEADER], 10) < options.retries)) {
93
- await this.sendToQueue(queue, RabbitMq.parseMsg(msg).content, options, {
94
- ...msg.properties.headers,
95
- [consts_1.RETRY_HEADER]: msg.properties.headers[consts_1.RETRY_HEADER]
96
- ? msg.properties.headers[consts_1.RETRY_HEADER] + 1
97
- : 1,
98
- });
99
- }
100
- else {
101
- const deadQueue = `${queue}-dead`;
102
- await this.sendToQueue(deadQueue, RabbitMq.parseMsg(msg).content, deadQueueOptions, {
103
- ...msg.properties.headers,
104
- [consts_1.RETRY_HEADER]: msg.properties.headers[consts_1.RETRY_HEADER]
105
- ? msg.properties.headers[consts_1.RETRY_HEADER] + 1
106
- : 1,
107
- });
108
- }
109
- debug('rabbit nacking message', { deliveryTag: msg.fields.deliveryTag });
110
- await channel.ack(msg);
111
- }
112
- else {
113
- logger_1.default.error('no channel or msg', {
114
- msg,
115
- });
116
- }
117
- };
118
- this.em = new events_1.EventEmitter();
119
- this.channel = null;
120
- this.publishChannelSetupPromise = null;
121
- this.connection = null;
122
- this.creatingConnection = false;
123
- this.exchanges = {};
124
- this.queues = {};
125
- this.queueSetupPromises = {};
126
- this.assertExchangePromises = {};
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.channel = null;
203
- this.consumersTags = [];
204
- debug('rabbit: connection closed');
205
- if (this.options?.disableReconnect) {
206
- logger_1.default.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
207
- this.blockReconnect = true;
208
- }
209
- else {
210
- logger_1.default.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
211
- }
212
- });
213
- this.connection.once('connect', async () => {
214
- debug('rabbit: connection established');
215
- this.creatingConnection = false;
216
- this.em.emit(consts_1.CONNECTION_CREATED_CONST, connection);
217
- isResolved = true;
218
- resolve(connection);
219
- });
220
- });
221
- }
222
- async getNewChannel({ name = (0, utils_1.rand)().toString(), onClose = null, options = {} } = {}) {
223
- let connection;
224
- try {
225
- connection = await this.getConnection();
226
- }
227
- catch (e) {
228
- logger_1.default.error(`rabbit: error on get connection for new channel ${name} `, { e });
229
- throw e;
230
- }
231
- const channel = connection.createChannel({ ...options });
232
- (0, events_1.once)(channel, 'close').then((args) => {
233
- logger_1.default.error(`rabbit: channel ${name} closed`);
234
- onClose?.(args);
235
- });
236
- try {
237
- await (0, events_1.once)(channel, 'connect');
238
- debug(`rabbit: channel ${name} CONNECTED`);
239
- return channel;
240
- }
241
- catch (err) {
242
- logger_1.default.error(`rabbit: channel error ${name} error`, { err });
243
- throw err;
244
- }
245
- }
246
- async assertChannel({ force = false } = {}) {
247
- if (!this.publishChannelSetupPromise) {
248
- this.publishChannelSetupPromise = new Promise(async (resolve, reject) => {
249
- if (this.channel && !force) {
250
- return resolve(this.channel);
251
- }
252
- try {
253
- const channel = await this.getNewChannel({});
254
- channel.on('error', (err) => {
255
- logger_1.default.error('rabbit: channel error', { err });
256
- });
257
- this.channel = channel;
258
- resolve(channel);
259
- }
260
- catch (e) {
261
- reject(e);
262
- }
263
- });
264
- }
265
- return this.publishChannelSetupPromise;
266
- }
267
- async assertExchange(exchangeName, options) {
268
- const channel = await this.assertChannel();
269
- if (this.exchanges[exchangeName]) {
270
- delete this.assertExchangePromises[exchangeName];
271
- return this.exchanges[exchangeName];
272
- }
273
- if (this.assertExchangePromises[exchangeName]) {
274
- return this.assertExchangePromises[exchangeName];
275
- }
276
- this.assertExchangePromises[exchangeName] = (0, utils_1.assertExchangeFanout)(channel, exchangeName);
277
- this.exchanges[exchangeName] = await this.assertExchangePromises[exchangeName];
278
- return this.exchanges[exchangeName];
279
- }
280
- async getQueueLength(queue) {
281
- RabbitMq.validateName('queue', queue);
282
- const { channel } = this;
283
- if (!channel) {
284
- throw new Error('channel is not defined');
285
- }
286
- debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
287
- return channel?.checkQueue(queue);
288
- }
289
- async deleteQueue(queue) {
290
- RabbitMq.validateName('queue', queue);
291
- const channel = await this.assertChannel();
292
- logger_1.default.info('rabbit: deleting queue', { queue });
293
- const deleteQueueRes = await channel.deleteQueue(queue);
294
- debug('queue deleted', deleteQueueRes);
295
- return deleteQueueRes;
296
- }
297
- async bindQueue(queue, exchange) {
298
- const channel = await this.assertChannel();
299
- await channel.addSetup((setupChannel) => setupChannel.bindQueue(queue, exchange, ''));
300
- return channel.bindQueue(queue, exchange, '');
301
- }
302
- async setupQueue(queueName, options) {
303
- let queue;
304
- try {
305
- const channel = await this.assertChannel();
306
- debug('assertQueue->channel.addSetup', { queueName });
307
- await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, options));
308
- debug('assertQueue->channel.assertQueue', { queueName });
309
- queue = await channel.assertQueue(queueName, options);
310
- }
311
- catch (e) {
312
- logger_1.default.error('rabbit: assertQueue error', { queueName, options, error: e });
313
- if (!this.options?.dontRetryAssert) {
314
- debug('retrying assertQueue', { queueName });
315
- const channel = await this.assertChannel({ force: true });
316
- await this.deleteQueue(queueName);
317
- debug('retrying assertQueue->channel.addSetup', { queueName });
318
- await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, options));
319
- debug('retrying assertQueue->channel.assertQueue', { queueName });
320
- queue = await channel.assertQueue(queueName, options);
321
- }
322
- else {
323
- throw e;
324
- }
325
- }
326
- this.queues[queueName] = queueName;
327
- return queue;
328
- }
329
- async assertQueue(queueName, options) {
330
- RabbitMq.validateName('queue', queueName);
331
- if (this.queues[queueName]) {
332
- delete this.queueSetupPromises[queueName];
333
- return this.queues[queueName];
334
- }
335
- if (this.queueSetupPromises[queueName]) {
336
- return this.queueSetupPromises[queueName];
337
- }
338
- this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
339
- return this.queueSetupPromises[queueName];
340
- }
341
- saveConsumer(queue, callback, options) {
342
- const isConsumerExist = this.consumers.some((consumer) => consumer.queue === queue);
343
- if (!isConsumerExist) {
344
- logger_1.default.info(`rabbit: consumer: ${queue} saved in consumer array`);
345
- this.consumers.push({
346
- queue,
347
- callback,
348
- options,
349
- });
350
- }
351
- }
352
- async consume(queue, callback, options) {
353
- await this.consumeFromRabbit(queue, callback, options);
354
- }
355
- async lockRedisIfNeeded(msg, options) {
356
- const { properties: { headers } } = msg;
357
- const timestamp = headers?.creationTimestamp;
358
- let releaseLock = null;
359
- if (options.useConsumeWithLock && timestamp && headers?.redisTimestampValidationKey && this.redisLock) {
360
- releaseLock = await this.redisLock(headers.redisTimestampValidationKey, options?.lockTimeout || consts_1.DEFAULT_LOCK_TIMEOUT);
361
- }
362
- return releaseLock;
363
- }
364
- async unlockRedisIfNeeded(releaseLock) {
365
- if (this.redisLock && releaseLock) {
366
- await releaseLock();
367
- }
368
- }
369
- async consumeFromRabbit(queue, callback, options) {
370
- const optionsWithDefaults = { ...consts_1.DEFAULT_OPTIONS, ...options };
371
- RabbitMq.validateName('queue', queue);
372
- this.saveConsumer(queue, callback, options);
373
- const uniqueId = (0, node_crypto_1.randomUUID)();
374
- const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace, } = optionsWithDefaults;
375
- if (useConsumeWithLock) {
376
- if (!this.redisLock) {
377
- throw new Error('Usage of consumeWithLock requires RedisInstance');
378
- }
379
- logger_1.default.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
380
- }
381
- const channel = await this.getNewChannel({});
382
- return channel.addSetup(async (confirmChannel) => {
383
- await confirmChannel.assertQueue(queue, options ? { messageTtl: options.messageTtl } : undefined);
384
- await confirmChannel.prefetch(limit, true);
385
- const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
386
- if (!msg) {
387
- return null;
388
- }
389
- const traceId = msg.properties.headers[consts_1.TRACING_HEADER];
390
- const userId = msg.properties.headers[consts_1.USER_TRACING_HEADER];
391
- const parsedMessage = RabbitMq.parseMsg(msg);
392
- const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
393
- const trace = (0, zehut_1.newTrace)(zehut_1.traceTypes.RABBIT);
394
- // setting also outbreak trace as part of legacy code
395
- const outbreakTrace = zehut_1.outbreak.newTrace(zehut_1.traceTypes.RABBIT);
396
- // enableRabbitTrace is a flag to protect from different flows that doesn't work with permission
397
- // and we don't want to fail the flow because of it
398
- if (userId && enableRabbitTrace) {
399
- try {
400
- await Promise.all([
401
- (0, zehut_1.createOrSetRabbitTrace)(trace, userId),
402
- (0, zehut_1.createOrSetRabbitTrace)(outbreakTrace, userId),
403
- ]);
404
- }
405
- catch (e) {
406
- logger_1.default.error('rabbit: failed to setRabbitTrace', { userId, e });
407
- return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
408
- }
409
- }
410
- if (traceId) {
411
- trace?.context?.set(consts_1.TRACING_HEADER, traceId);
412
- outbreakTrace?.context.set(consts_1.TRACING_HEADER, traceId);
413
- }
414
- if (auditContext) {
415
- await auditContext(queue);
416
- }
417
- const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
418
- if (!shouldConsume) {
419
- await this.unlockRedisIfNeeded(releaseLock);
420
- return this.ack(confirmChannel, msg)(msg);
421
- }
422
- let messageAcked = false;
423
- // setting the localAck function to be used in the callback
424
- const localAck = async () => {
425
- if (messageAcked) {
426
- return;
427
- }
428
- messageAcked = true;
429
- return this.ack(confirmChannel, msg, true, releaseLock)(msg);
430
- };
431
- const localNack = async (_, nackOptions = {}) => {
432
- if (messageAcked) {
433
- return;
434
- }
435
- debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
436
- messageAcked = true;
437
- return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
438
- };
439
- try {
440
- await callback(parsedMessage, localAck, localNack);
441
- }
442
- catch (e) {
443
- await localNack(msg);
444
- }
445
- }, types_1.CONSUMER_DEFAULT_OPTIONS);
446
- if (!consumerTag) {
447
- logger_1.default.error(`rabbit: failed to consume from queue ${queue}`);
448
- }
449
- else {
450
- logger_1.default.info(`rabbit: adding tag ${consumerTag} to the array.`);
451
- this.consumersTags.push([confirmChannel, consumerTag]);
452
- }
453
- });
454
- }
455
- async consumeFromExchange(queue, exchange, callback, options) {
456
- const optionsWithDefaults = { ...consts_1.DEFAULT_OPTIONS, ...options };
457
- RabbitMq.validateName('exchange', exchange);
458
- RabbitMq.validateName('queue', queue);
459
- const { limit, deadMessageTtl } = optionsWithDefaults;
460
- await this.saveConsumer(queue, callback, options);
461
- const channel = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
462
- return channel.addSetup(async (c) => {
463
- const assertExchange = await (0, utils_1.assertExchangeFanout)(c, exchange);
464
- await c.assertQueue(queue);
465
- this.exchanges[exchange] = assertExchange;
466
- await c.prefetch(limit, true);
467
- return Promise.all([
468
- c.bindQueue(queue, exchange, ''),
469
- this.consume(queue, callback, options),
470
- ]);
471
- });
472
- }
473
- async publish(exchange, content, customHeaders) {
474
- return (0, utils_1.wrapSetImmediate)(async () => {
475
- RabbitMq.validateName('exchange', exchange);
476
- const channel = await this.assertChannel();
477
- await this.assertExchange(exchange);
478
- await channel.publish(exchange, '', Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
479
- });
480
- }
481
- async sendToQueue(queue, content, options, customHeaders) {
482
- try {
483
- await this.assertChannel();
484
- }
485
- catch (e) {
486
- logger_1.default.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
487
- throw e;
488
- }
489
- try {
490
- RabbitMq.validateName('queue', queue);
491
- await this.assertQueue(queue, options);
492
- }
493
- catch (e) {
494
- logger_1.default.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
495
- throw e;
496
- }
497
- try {
498
- const res = await this.channel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
499
- debug(`rabbit: sending to queue ${queue}`, { res });
500
- return res;
501
- }
502
- catch (e) {
503
- const isConnected = await this.isConnected();
504
- logger_1.default.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
505
- throw e;
506
- }
507
- }
508
- async isConnected() {
509
- const connection = await this.getConnection();
510
- const isConnected = connection.isConnected();
511
- if (!isConnected) {
512
- logger_1.default.error('rabbit: isConnected - false');
513
- return false;
514
- }
515
- const channel = await this.assertChannel();
516
- try {
517
- await Promise.all([
518
- channel.waitForConnect(),
519
- ...this.consumers.map((c) => channel.checkQueue(c.queue)),
520
- ]);
521
- }
522
- catch (e) {
523
- logger_1.default.error('rabbit: isConnected - false');
524
- return false;
525
- }
526
- logger_1.default.info('rabbit: isConnected - true');
527
- return true;
528
- }
529
- async gracefulShutdown(signal) {
530
- const tagsNumber = this.consumersTags.length;
531
- logger_1.default.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
532
- const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
533
- // Clean the array to avoid race
534
- this.consumersTags = [];
535
- const results = await Promise.allSettled(cancelTagPromises);
536
- const rejected = results.filter((p) => p.status === 'rejected');
537
- if (rejected.length > 0) {
538
- logger_1.default.warn(`rabbit: [gracefully-shutdown] #${rejected.length}/${tagsNumber} tags failed to cancel: ${rejected}`);
539
- }
540
- else {
541
- logger_1.default.info('rabbit: [gracefully-shutdown] all tags successfully canceled.');
542
- }
543
- }
544
- }
545
- 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,43 +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 interface AssertExchangePromisesDictionary {
12
- [key: string]: Promise<Replies.AssertExchange> | undefined;
13
- }
14
- export type CustomMessageHeaders = {
15
- redisTimestampValidationKey?: string;
16
- };
17
- export type RedisLockType = (args0?: string, arg1?: number) => Promise<any>;
18
- export type ConsumeMessageOrNull = ConsumeMessage | null;
19
- export interface ConsumeOptions {
20
- retries?: number;
21
- deadMessageTtl?: number;
22
- messageTtl?: number;
23
- limit?: number;
24
- lockTimeout?: number;
25
- useConsumeWithLock?: boolean;
26
- auditContext?: any;
27
- enableRabbitTrace?: boolean;
28
- }
29
- export type CallbackFunction = (msg: ConsumeMessage, ack: any, nack: any) => Promise<any>;
30
- export type newChannelOpts = {
31
- name?: string;
32
- onClose?: null | ((args: any | null) => void);
33
- };
34
- export type assertChannelOpts = {
35
- channelName?: string;
36
- force?: boolean;
37
- };
38
- export type AfConsumer = {
39
- queue: string;
40
- callback: CallbackFunction;
41
- options: ConsumeOptions | undefined;
42
- };
43
- 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, Replies } from 'amqplib';
3
- export declare const assertExchangeFanout: (c: ChannelWrapper | ConfirmChannel, exchangeName: string) => Promise<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;