@autofleet/rabbit 3.2.21-beta.0 → 3.2.21-beta.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ import { EventEmitter } from 'events';
3
3
  import { AmqpConnectionManager, ChannelWrapper, CreateChannelOpts } from 'amqp-connection-manager';
4
4
  import { ConfirmChannel, ConsumeMessage, Options, Replies } from 'amqplib';
5
5
  import { RedisConfig } from './lib/redis';
6
- import { CallbackFunction, ConsumeMessageOrNull, ConsumeOptions, CustomMessageHeaders, QueuesCache, RedisLockType, ExchangesCache, QueueSetupPromisesDictionary } from './lib/types';
6
+ import { CallbackFunction, ConsumeMessageOrNull, ConsumeOptions, CustomMessageHeaders, QueuesCache, RedisLockType, ExchangesCache, QueueSetupPromisesDictionary, AssertExchangePromisesDictionary } from './lib/types';
7
7
  export interface IAfRabbitMq {
8
8
  ack: any;
9
9
  nack: any;
@@ -66,6 +66,7 @@ declare class RabbitMq implements IAfRabbitMq {
66
66
  exchanges: ExchangesCache;
67
67
  queues: QueuesCache;
68
68
  queueSetupPromises: QueueSetupPromisesDictionary;
69
+ assertExchangePromises: AssertExchangePromisesDictionary;
69
70
  options: AfRabbitOptions | undefined;
70
71
  redisClient: any;
71
72
  redisLock?: RedisLockType;
@@ -93,6 +94,7 @@ declare class RabbitMq implements IAfRabbitMq {
93
94
  consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any>;
94
95
  publish(exchange: string, content: any, customHeaders?: any): Promise<boolean>;
95
96
  sendToQueue(queue: string, content: any, options?: any, customHeaders?: any): Promise<boolean | undefined>;
97
+ sendToCeleryQueue(queue: string, taskName: string, data: any, options?: any, customHeaders?: any): Promise<boolean | undefined>;
96
98
  isConnected(): Promise<boolean>;
97
99
  gracefulShutdown(signal: string): Promise<void>;
98
100
  }
package/dist/index.js CHANGED
@@ -6,7 +6,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const events_1 = require("events");
8
8
  const util_1 = require("util");
9
- const lodash_1 = __importDefault(require("lodash"));
10
9
  const moment_1 = __importDefault(require("moment"));
11
10
  const redis_lock_1 = __importDefault(require("redis-lock"));
12
11
  const amqp_connection_manager_1 = require("amqp-connection-manager");
@@ -124,6 +123,7 @@ class RabbitMq {
124
123
  this.exchanges = {};
125
124
  this.queues = {};
126
125
  this.queueSetupPromises = {};
126
+ this.assertExchangePromises = {};
127
127
  this.consumers = [];
128
128
  this.options = options;
129
129
  this.redisClient = redisConfig && (0, redis_1.default)(redisConfig);
@@ -199,6 +199,7 @@ class RabbitMq {
199
199
  }
200
200
  });
201
201
  this.connection.on('disconnect', ({ err }) => {
202
+ // this.channel = null;
202
203
  this.consumersTags = [];
203
204
  debug('rabbit: connection closed');
204
205
  if (this.options?.disableReconnect) {
@@ -266,11 +267,15 @@ class RabbitMq {
266
267
  async assertExchange(exchangeName, options) {
267
268
  const channel = await this.assertChannel();
268
269
  if (this.exchanges[exchangeName]) {
270
+ delete this.assertExchangePromises[exchangeName];
269
271
  return this.exchanges[exchangeName];
270
272
  }
271
- const exchange = await (0, utils_1.assertExchangeFanout)(channel, exchangeName);
272
- this.exchanges[exchangeName] = exchange;
273
- return exchange;
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];
274
279
  }
275
280
  async getQueueLength(queue) {
276
281
  RabbitMq.validateName('queue', queue);
@@ -377,7 +382,6 @@ class RabbitMq {
377
382
  return channel.addSetup(async (confirmChannel) => {
378
383
  await confirmChannel.assertQueue(queue, options ? { messageTtl: options.messageTtl } : undefined);
379
384
  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
385
  const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
382
386
  if (!msg) {
383
387
  return null;
@@ -438,7 +442,7 @@ class RabbitMq {
438
442
  catch (e) {
439
443
  await localNack(msg);
440
444
  }
441
- }, channelConsumerOptions);
445
+ }, types_1.CONSUMER_DEFAULT_OPTIONS);
442
446
  if (!consumerTag) {
443
447
  logger_1.default.error(`rabbit: failed to consume from queue ${queue}`);
444
448
  }
@@ -501,6 +505,47 @@ class RabbitMq {
501
505
  throw e;
502
506
  }
503
507
  }
508
+ async sendToCeleryQueue(queue, taskName, data, options, customHeaders) {
509
+ try {
510
+ await this.assertChannel();
511
+ }
512
+ catch (e) {
513
+ logger_1.default.error(`rabbit sendToCeleryQueue: failed to assert channel when sending to queue ${queue}`, { e });
514
+ throw e;
515
+ }
516
+ try {
517
+ RabbitMq.validateName('queue', queue);
518
+ await this.assertQueue(queue, options);
519
+ }
520
+ catch (e) {
521
+ logger_1.default.error(`rabbit sendToCeleryQueue: failed to assert queue ${queue}`, { e });
522
+ throw e;
523
+ }
524
+ try {
525
+ // Create Celery-compatible message format
526
+ const celeryMessage = {
527
+ task: taskName,
528
+ id: (0, node_crypto_1.randomUUID)(),
529
+ args: [data],
530
+ };
531
+ const celeryHeaders = {
532
+ ...RabbitMq.getPublishOptions(customHeaders),
533
+ properties: {
534
+ delivery_mode: 2,
535
+ content_type: 'application/json',
536
+ content_encoding: 'utf-8',
537
+ },
538
+ };
539
+ const res = await this.channel?.sendToQueue(queue, Buffer.from(JSON.stringify(celeryMessage)), celeryHeaders);
540
+ debug(`rabbit: sending to celery queue ${queue}`, { res });
541
+ return res;
542
+ }
543
+ catch (e) {
544
+ const isConnected = await this.isConnected();
545
+ logger_1.default.error(`rabbit sendToCeleryQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
546
+ throw e;
547
+ }
548
+ }
504
549
  async isConnected() {
505
550
  const connection = await this.getConnection();
506
551
  const isConnected = connection.isConnected();
@@ -8,6 +8,9 @@ export interface QueuesCache {
8
8
  export interface QueueSetupPromisesDictionary {
9
9
  [key: string]: Promise<Replies.AssertQueue> | undefined;
10
10
  }
11
+ export interface AssertExchangePromisesDictionary {
12
+ [key: string]: Promise<Replies.AssertExchange> | undefined;
13
+ }
11
14
  export type CustomMessageHeaders = {
12
15
  redisTimestampValidationKey?: string;
13
16
  };
@@ -22,7 +25,6 @@ export interface ConsumeOptions {
22
25
  useConsumeWithLock?: boolean;
23
26
  auditContext?: any;
24
27
  enableRabbitTrace?: boolean;
25
- channelConsumeOptions?: Options.Consume;
26
28
  }
27
29
  export type CallbackFunction = (msg: ConsumeMessage, ack: any, nack: any) => Promise<any>;
28
30
  export type newChannelOpts = {
@@ -1,5 +1,5 @@
1
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>;
2
+ import { ConfirmChannel, Replies } from 'amqplib';
3
+ export declare const assertExchangeFanout: (c: ChannelWrapper | ConfirmChannel, exchangeName: string) => Promise<Replies.AssertExchange>;
4
4
  export declare const wrapSetImmediate: (callback: () => any) => Promise<any>;
5
5
  export declare const rand: () => number;
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.1",
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,60 @@ class RabbitMq implements IAfRabbitMq {
685
697
  }
686
698
  }
687
699
 
700
+ async sendToCeleryQueue(
701
+ queue: string,
702
+ taskName: string,
703
+ data: any,
704
+ options?: any,
705
+ customHeaders?: any,
706
+ ): Promise<boolean | undefined> {
707
+ try {
708
+ await this.assertChannel();
709
+ } catch (e) {
710
+ logger.error(`rabbit sendToCeleryQueue: failed to assert channel when sending to queue ${queue}`, { e });
711
+ throw e;
712
+ }
713
+
714
+ try {
715
+ RabbitMq.validateName('queue', queue);
716
+ await this.assertQueue(queue, options);
717
+ } catch (e) {
718
+ logger.error(`rabbit sendToCeleryQueue: failed to assert queue ${queue}`, { e });
719
+ throw e;
720
+ }
721
+
722
+ try {
723
+ // Create Celery-compatible message format
724
+ const celeryMessage = {
725
+ task: taskName,
726
+ id: randomUUID(),
727
+ args: [data],
728
+ };
729
+
730
+ const celeryHeaders = {
731
+ ...RabbitMq.getPublishOptions(customHeaders),
732
+ properties: {
733
+ delivery_mode: 2,
734
+ content_type: 'application/json',
735
+ content_encoding: 'utf-8',
736
+ },
737
+ };
738
+
739
+ const res = await this.channel?.sendToQueue(
740
+ queue,
741
+ Buffer.from(JSON.stringify(celeryMessage)),
742
+ celeryHeaders,
743
+ );
744
+
745
+ debug(`rabbit: sending to celery queue ${queue}`, { res });
746
+ return res;
747
+ } catch (e) {
748
+ const isConnected = await this.isConnected();
749
+ logger.error(`rabbit sendToCeleryQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
750
+ throw e;
751
+ }
752
+ }
753
+
688
754
  async isConnected() : Promise<boolean> {
689
755
  const connection = await this.getConnection();
690
756
  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 {