@autofleet/rabbit 3.2.5 → 3.2.6-testing

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/.env ADDED
@@ -0,0 +1,3 @@
1
+ #RABBITMQ_SERVICE_HOST=
2
+ RABBITMQ_USERNAME=default_user_w2_vEYNVrLWiAnWKmPH
3
+ RABBITMQ_PASSWORD=GUAPvNXJbkgbkGeeFUYbZfOxVfR4cuf8
package/dist/index.d.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  /// <reference types="node" />
2
2
  import { EventEmitter } from 'events';
3
- import { AmqpConnectionManager, ChannelWrapper, CreateChannelOpts } from 'amqp-connection-manager';
3
+ import { AmqpConnectionManager, ChannelWrapper } from 'amqp-connection-manager';
4
4
  import { ConfirmChannel, ConsumeMessage, Options, Replies } from 'amqplib';
5
5
  import { RedisConfig } from './lib/redis';
6
- import { TRACING_HEADER, USER_TRACING_HEADER } from './lib/consts';
7
6
  import { CallbackFunction, ConsumeMessageOrNull, ConsumeOptions, CustomMessageHeaders, QueuesCache, RedisLockType, ExchangesCache } from './lib/types';
8
7
  export interface IAfRabbitMq {
9
8
  ack: any;
@@ -17,9 +16,6 @@ export interface IAfRabbitMq {
17
16
  sendToQueue: any;
18
17
  redisClient?: any;
19
18
  }
20
- interface NackOptions {
21
- skipRetry?: boolean;
22
- }
23
19
  export interface AfRabbitOptions {
24
20
  disableReconnect?: boolean;
25
21
  /**
@@ -28,18 +24,16 @@ export interface AfRabbitOptions {
28
24
  */
29
25
  dontGracefulShutdown?: boolean;
30
26
  /**
31
- * dont retry on creation error
27
+ * retry on creation error
32
28
  * @default false
33
29
  */
34
- dontRetryAssert?: boolean;
35
- rabbitHost?: string;
30
+ retryAssert?: boolean;
36
31
  }
37
- declare type newChannelOpts = {
32
+ type newChannelOpts = {
38
33
  name?: string;
39
34
  onClose?: null | ((args: any | null) => void);
40
- options?: CreateChannelOpts | undefined;
41
35
  };
42
- declare type assertChannelOpts = {
36
+ type assertChannelOpts = {
43
37
  channelName?: string;
44
38
  force?: boolean;
45
39
  };
@@ -48,14 +42,14 @@ declare class RabbitMq implements IAfRabbitMq {
48
42
  static validateName(type: string, name: string): void;
49
43
  static getPublishOptions(customHeaders?: CustomMessageHeaders): {
50
44
  timestamp: number;
51
- timeout: number;
52
45
  headers: {
53
- "x-af-user-id": any;
54
46
  "x-trace-id": any;
55
47
  redisTimestampValidationKey?: string | undefined;
56
48
  creationTimestamp: number;
57
49
  };
58
50
  };
51
+ PUBLISH_TIMEOUT: number;
52
+ PUBLISH_ERROR_MSG: string;
59
53
  DISCONNECT_MSG: string;
60
54
  RECONNECT_MSG: string;
61
55
  channel: ChannelWrapper | null;
@@ -73,10 +67,10 @@ declare class RabbitMq implements IAfRabbitMq {
73
67
  private consumers;
74
68
  constructor(options?: AfRabbitOptions, redisConfig?: RedisConfig);
75
69
  private shouldConsumeMessageByTimestamp;
76
- ack: (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp?: boolean, releaseLock?: null) => (userMsg: ConsumeMessage) => Promise<any>;
77
- nack: (channel: ConfirmChannel, queue: string, options: any, deadQueueOptions: Options.AssertQueue, msg: ConsumeMessageOrNull, releaseLock: any) => (userMsg: ConsumeMessageOrNull, { skipRetry, }?: NackOptions) => Promise<any>;
70
+ ack: (channel: ChannelWrapper, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp?: boolean, releaseLock?: null) => (userMsg: ConsumeMessage) => Promise<any>;
71
+ nack: (channel: ChannelWrapper, queue: string, options: any, deadQueueOptions: Options.AssertQueue, msg: ConsumeMessageOrNull, releaseLock: any) => (userMsg: ConsumeMessageOrNull, { skipRetry, }?: any) => Promise<any>;
78
72
  getConnection(): Promise<AmqpConnectionManager>;
79
- getNewChannel({ name, onClose, options }?: newChannelOpts): Promise<ChannelWrapper>;
73
+ getNewChannel({ name, onClose }?: newChannelOpts): Promise<ChannelWrapper>;
80
74
  assertChannel({ force }?: assertChannelOpts): Promise<ChannelWrapper>;
81
75
  assertExchange(exchangeName: string, options?: any): Promise<any>;
82
76
  getQueueLength(queue: string): Promise<Replies.AssertQueue>;
@@ -84,6 +78,7 @@ declare class RabbitMq implements IAfRabbitMq {
84
78
  bindQueue(queue: string, exchange: string): Promise<void>;
85
79
  assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any>;
86
80
  private saveConsumer;
81
+ private loadConsumers;
87
82
  consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any>;
88
83
  private lockRedisIfNeeded;
89
84
  private unlockRedisIfNeeded;
package/dist/index.js CHANGED
@@ -9,19 +9,50 @@ const util_1 = require("util");
9
9
  const moment_1 = __importDefault(require("moment"));
10
10
  const redis_lock_1 = __importDefault(require("redis-lock"));
11
11
  const amqp_connection_manager_1 = require("amqp-connection-manager");
12
- const zehut_1 = require("@autofleet/zehut");
12
+ const outbreak_1 = require("@autofleet/outbreak");
13
13
  const logger_1 = __importDefault(require("./logger"));
14
14
  const rabbitError_1 = __importDefault(require("./lib/rabbitError"));
15
15
  const redis_1 = __importDefault(require("./lib/redis"));
16
16
  const utils_1 = require("./lib/utils");
17
17
  const consts_1 = require("./lib/consts");
18
- const types_1 = require("./lib/types");
19
18
  // const debug = nodeDebug('af-rabbitmq')
20
19
  const debug = logger_1.default.info;
21
- const PUBLISH_TIMEOUT = 1000 * 10;
20
+ const USERNAME = process.env.RABBITMQ_USERNAME || 'guest';
21
+ const PASSWORD = process.env.RABBITMQ_PASSWORD || 'guest';
22
+ const HOST = process.env.RABBITMQ_SERVICE_HOST || 'localhost';
22
23
  const HEARTBEAT = '60';
23
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, outbreak_1.getCurrentContext)();
44
+ return {
45
+ timestamp: (0, moment_1.default)().unix(),
46
+ headers: {
47
+ creationTimestamp: (0, moment_1.default)().valueOf(),
48
+ ...customHeaders,
49
+ [consts_1.TRACING_HEADER]: trace?.context?.get(consts_1.TRACING_HEADER),
50
+ },
51
+ };
52
+ }
24
53
  constructor(options, redisConfig) {
54
+ this.PUBLISH_TIMEOUT = Number(process.env.RABBITMQ_PUBLISH_TIMEOUT) || 60000;
55
+ this.PUBLISH_ERROR_MSG = `rabbit: publish timeout(${this.PUBLISH_TIMEOUT}ms) has pass, exchange: `;
25
56
  this.DISCONNECT_MSG = 'rabbit: connection disconnect';
26
57
  this.RECONNECT_MSG = 'rabbit: connection disconnect - reconnecting';
27
58
  this.consumers = [];
@@ -75,6 +106,7 @@ class RabbitMq {
75
106
  }
76
107
  else {
77
108
  logger_1.default.error('no channel or msg', {
109
+ channel: channel ? channel.name : '',
78
110
  msg,
79
111
  });
80
112
  }
@@ -86,9 +118,9 @@ class RabbitMq {
86
118
  this.exchanges = {};
87
119
  this.queues = {};
88
120
  this.options = options;
89
- this.redisClient = redisConfig && redis_1.default(redisConfig);
121
+ this.redisClient = redisConfig && (0, redis_1.default)(redisConfig);
90
122
  if (this.redisClient) {
91
- this.redisLock = util_1.promisify(redis_lock_1.default(this.redisClient));
123
+ this.redisLock = (0, util_1.promisify)((0, redis_lock_1.default)(this.redisClient));
92
124
  }
93
125
  this.consumersTags = [];
94
126
  logger_1.default.info(`rabbit: [gracefully-shutdown] adding gracefully shutdown for process.pid ${process.pid}`);
@@ -101,41 +133,8 @@ class RabbitMq {
101
133
  });
102
134
  }
103
135
  }
104
- static parseMsg(msg) {
105
- let { content } = msg;
106
- content = content.toString();
107
- try {
108
- content = JSON.parse(content);
109
- }
110
- catch (e) { }
111
- return {
112
- ...msg,
113
- content,
114
- };
115
- }
116
- static validateName(type, name) {
117
- if (!name || name === '') {
118
- throw new rabbitError_1.default(`error while using ${type} with no name`);
119
- }
120
- }
121
- static getPublishOptions(customHeaders = {}) {
122
- const trace = zehut_1.getCurrentPayload();
123
- const user = trace?.context?.get(consts_1.USER_OBJECT);
124
- const traceId = trace?.context?.get(consts_1.TRACING_HEADER);
125
- const outbreakTrace = zehut_1.outbreak.getCurrentContext();
126
- return {
127
- timestamp: moment_1.default().unix(),
128
- timeout: PUBLISH_TIMEOUT,
129
- headers: {
130
- creationTimestamp: moment_1.default().valueOf(),
131
- ...customHeaders,
132
- [consts_1.USER_TRACING_HEADER]: user?.id,
133
- [consts_1.TRACING_HEADER]: traceId || outbreakTrace?.context?.get(consts_1.TRACING_HEADER),
134
- },
135
- };
136
- }
137
136
  async getConnection() {
138
- return new Promise(async (resolve, reject) => {
137
+ return new Promise(async (resolve) => {
139
138
  if (this.blockReconnect) {
140
139
  debug('rabbit: block reconnect');
141
140
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -143,57 +142,29 @@ class RabbitMq {
143
142
  return resolve();
144
143
  }
145
144
  if (this.connection !== null) {
145
+ debug('rabbit: connection already exist');
146
146
  if (this.options?.disableReconnect || this.connection?.isConnected()) {
147
- debug('rabbit: connection - is connected');
148
147
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
149
148
  // @ts-ignore
150
149
  return resolve(this.connection);
151
150
  }
152
- debug('rabbit: connection - reconnecting');
151
+ debug('rabbit: connection already exist - reconnecting');
153
152
  }
154
153
  if (this.creatingConnection) {
155
154
  debug('rabbit: creating connection emi');
156
155
  this.em.once(consts_1.CONNECTION_CREATED_CONST, resolve);
157
- this.em.once(consts_1.CONNECTION_FAILED_CONST, reject);
158
156
  return;
159
157
  }
160
158
  this.creatingConnection = true;
161
- let isResolved = false;
162
- // It is import to use it as a function and not as a variable
163
- // because of k8s changes the env variables
164
- // and we want to use the new values
165
- const findServers = () => {
166
- const userName = process.env.RABBITMQ_USERNAME || 'guest';
167
- const password = process.env.RABBITMQ_PASSWORD || 'guest';
168
- const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
169
- debug('rabbit: creating connection', { host, userName, HEARTBEAT });
170
- return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
171
- };
172
- const defaultUrls = findServers();
173
- const connection = await amqp_connection_manager_1.connect(defaultUrls, {
174
- findServers,
175
- });
159
+ debug('rabbit: creating connection', { HOST, USERNAME, HEARTBEAT });
160
+ const connection = await (0, amqp_connection_manager_1.connect)([`amqp://${USERNAME}:${PASSWORD}@${HOST}?heartbeat=${HEARTBEAT}}`]);
176
161
  this.connection = connection;
177
- this.connection.on('error', (err) => {
178
- logger_1.default.error('rabbit: connection error', { err });
179
- if (!isResolved) {
180
- isResolved = true;
181
- reject(err);
182
- this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
183
- }
184
- });
185
- this.connection.on('connectFailed', (err) => {
186
- this.consumersTags = [];
187
- logger_1.default.error('rabbit: connection connectFailed', { err });
188
- if (!isResolved) {
189
- isResolved = true;
190
- reject(err);
191
- this.em.emit(consts_1.CONNECTION_FAILED_CONST, err);
192
- }
193
- });
194
162
  this.connection.on('disconnect', ({ err }) => {
195
- this.consumersTags = [];
196
163
  debug('rabbit: connection closed');
164
+ this.exchanges = {};
165
+ this.queues = {};
166
+ this.connection = null;
167
+ this.channel = null;
197
168
  if (this.options?.disableReconnect) {
198
169
  logger_1.default.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
199
170
  this.blockReconnect = true;
@@ -204,26 +175,19 @@ class RabbitMq {
204
175
  });
205
176
  this.connection.once('connect', async () => {
206
177
  debug('rabbit: connection established');
178
+ await this.loadConsumers();
207
179
  this.creatingConnection = false;
208
180
  this.em.emit(consts_1.CONNECTION_CREATED_CONST, connection);
209
- isResolved = true;
210
181
  resolve(connection);
211
182
  });
212
183
  });
213
184
  }
214
- async getNewChannel({ name = utils_1.rand().toString(), onClose = null, options = {} } = {}) {
185
+ async getNewChannel({ name = '', onClose = null } = {}) {
215
186
  return new Promise(async (resolve, reject) => {
216
187
  const connection = await this.getConnection();
217
- const channel = connection.createChannel({
218
- ...options,
219
- });
220
- let isResolved = false;
188
+ const channel = connection.createChannel({});
221
189
  channel.on('error', (err) => {
222
- logger_1.default.error(`rabbit: channel error ${name} error`, { err });
223
- if (!isResolved) {
224
- isResolved = true;
225
- reject(err);
226
- }
190
+ logger_1.default.error(`rabbit: channel ${name} error`, { err });
227
191
  });
228
192
  channel.on('close', (...args) => {
229
193
  logger_1.default.error(`rabbit: channel ${name} closed`, { args });
@@ -233,7 +197,6 @@ class RabbitMq {
233
197
  });
234
198
  channel.once('connect', () => {
235
199
  debug(`rabbit: channel ${name} CONNECTED`);
236
- isResolved = true;
237
200
  resolve(channel);
238
201
  });
239
202
  });
@@ -244,9 +207,10 @@ class RabbitMq {
244
207
  return resolve(this.channel);
245
208
  }
246
209
  try {
247
- const channel = await this.getNewChannel({});
248
- channel.on('error', (err) => {
249
- logger_1.default.error('rabbit: channel error', { err });
210
+ const channel = await this.getNewChannel({
211
+ onClose: () => {
212
+ this.channel = null;
213
+ },
250
214
  });
251
215
  this.channel = channel;
252
216
  resolve(channel);
@@ -261,26 +225,25 @@ class RabbitMq {
261
225
  if (this.exchanges[exchangeName]) {
262
226
  return this.exchanges[exchangeName];
263
227
  }
264
- const exchange = await utils_1.assertExchangeFanout(channel, exchangeName);
228
+ const exchange = await (0, utils_1.assertExchangeFanout)(channel, exchangeName);
265
229
  this.exchanges[exchangeName] = exchange;
266
230
  return exchange;
267
231
  }
268
232
  async getQueueLength(queue) {
269
233
  RabbitMq.validateName('queue', queue);
270
- const { channel } = this;
271
- if (!channel) {
272
- throw new Error('channel is not defined');
273
- }
234
+ const channel = await this.assertChannel();
274
235
  debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
275
- return channel?.checkQueue(queue);
236
+ return channel.checkQueue(queue);
276
237
  }
277
238
  async deleteQueue(queue) {
278
- RabbitMq.validateName('queue', queue);
279
- const channel = await this.assertChannel();
280
- logger_1.default.info('rabbit: deleting queue', { queue });
281
- const deleteQueueRes = await channel.deleteQueue(queue);
282
- debug('queue deleted', deleteQueueRes);
283
- return deleteQueueRes;
239
+ return new Promise(async (resolve, reject) => {
240
+ RabbitMq.validateName('queue', queue);
241
+ const channel = await this.assertChannel();
242
+ logger_1.default.info('rabbit: deleting queue', { queue });
243
+ const deleteQueueRes = await channel.deleteQueue(queue);
244
+ debug('queue deleted', deleteQueueRes);
245
+ resolve(deleteQueueRes);
246
+ });
284
247
  }
285
248
  async bindQueue(queue, exchange) {
286
249
  const channel = await this.assertChannel();
@@ -302,7 +265,7 @@ class RabbitMq {
302
265
  }
303
266
  catch (e) {
304
267
  logger_1.default.error('rabbit: assertQueue error', { queueName, options, error: e });
305
- if (!this.options?.dontRetryAssert) {
268
+ if (this.options?.retryAssert) {
306
269
  debug('retrying assertQueue', { queueName });
307
270
  const channel = await this.assertChannel({ force: true });
308
271
  await this.deleteQueue(queueName);
@@ -325,8 +288,18 @@ class RabbitMq {
325
288
  options,
326
289
  });
327
290
  }
291
+ async loadConsumers() {
292
+ debug('rabbit: loading consumers', { consumers: this.consumers.length });
293
+ if (this.consumers.length > 0) {
294
+ await Promise.all(this.consumers.map((consumer) => this
295
+ .consumeFromRabbit(consumer.queue, consumer.callback, consumer.options)));
296
+ }
297
+ }
328
298
  async consume(queue, callback, options) {
329
- return this.consumeFromRabbit(queue, callback, options);
299
+ if (this.connection && !this.creatingConnection) {
300
+ this.consumeFromRabbit(queue, callback, options);
301
+ }
302
+ return this.saveConsumer(queue, callback, options);
330
303
  }
331
304
  async lockRedisIfNeeded(msg, options) {
332
305
  const { properties: { headers } } = msg;
@@ -345,83 +318,46 @@ class RabbitMq {
345
318
  async consumeFromRabbit(queue, callback, options) {
346
319
  const optionsWithDefaults = { ...consts_1.DEFAULT_OPTIONS, ...options };
347
320
  RabbitMq.validateName('queue', queue);
348
- const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace, } = optionsWithDefaults;
321
+ const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, } = optionsWithDefaults;
349
322
  if (useConsumeWithLock) {
350
323
  if (!this.redisLock) {
351
324
  throw new Error('Usage of consumeWithLock requires RedisInstance');
352
325
  }
353
326
  logger_1.default.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
354
327
  }
355
- const channel = await this.getNewChannel({ name: `consume-queue-${queue}` });
356
- return channel.addSetup(async (confirmChannel) => {
357
- await confirmChannel.assertQueue(queue, options ? { messageTtl: options.messageTtl } : undefined);
358
- await confirmChannel.prefetch(limit, true);
359
- const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
328
+ const channel = await this.getNewChannel({ name: `consume-${queue}` });
329
+ return channel.addSetup(async (c) => {
330
+ await c.assertQueue(queue, options ? { messageTtl: options.messageTtl } : undefined);
331
+ await c.prefetch(limit, true);
332
+ const { consumerTag } = await c.consume(queue, async (msg) => {
360
333
  if (!msg) {
361
334
  return null;
362
335
  }
363
336
  const traceId = msg.properties.headers[consts_1.TRACING_HEADER];
364
- const userId = msg.properties.headers[consts_1.USER_TRACING_HEADER];
365
- const parsedMessage = RabbitMq.parseMsg(msg);
366
- const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
367
- const trace = zehut_1.newTrace(zehut_1.traceTypes.RABBIT);
368
- // setting also outbreak trace as part of legacy code
369
- const outbreakTrace = zehut_1.outbreak.newTrace(zehut_1.traceTypes.RABBIT);
370
- // enableRabbitTrace is a flag to protect from different flows that doesn't work with permission
371
- // and we don't want to fail the flow because of it
372
- if (userId && enableRabbitTrace) {
373
- try {
374
- await Promise.all([
375
- zehut_1.createOrSetRabbitTrace(trace, userId),
376
- zehut_1.createOrSetRabbitTrace(outbreakTrace, userId),
377
- ]);
378
- }
379
- catch (e) {
380
- logger_1.default.error('rabbit: failed to setRabbitTrace', { userId, e });
381
- return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
382
- }
383
- }
384
337
  if (traceId) {
338
+ const trace = (0, outbreak_1.newTrace)(outbreak_1.traceTypes.RABBIT);
385
339
  trace?.context?.set(consts_1.TRACING_HEADER, traceId);
386
- outbreakTrace?.context.set(consts_1.TRACING_HEADER, traceId);
387
- }
388
- if (auditContext) {
389
- await auditContext(queue);
390
340
  }
341
+ const parsedMessage = RabbitMq.parseMsg(msg);
342
+ const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
391
343
  const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
392
344
  if (!shouldConsume) {
393
345
  await this.unlockRedisIfNeeded(releaseLock);
394
- return this.ack(confirmChannel, msg)(msg);
346
+ return this.ack(channel, msg)(msg);
395
347
  }
396
- let messageAcked = false;
397
- // setting the localAck function to be used in the callback
398
- const localAck = async () => {
399
- if (messageAcked) {
400
- return;
401
- }
402
- messageAcked = true;
403
- return this.ack(confirmChannel, msg, true, releaseLock)(msg);
404
- };
405
- const localNack = async (_, nackOptions = {}) => {
406
- if (messageAcked) {
407
- return;
408
- }
409
- messageAcked = true;
410
- return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
411
- };
412
348
  try {
413
- await callback(parsedMessage, localAck, localNack);
349
+ await callback(parsedMessage, this.ack(channel, msg, true, releaseLock), this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock));
414
350
  }
415
351
  catch (e) {
416
- await localNack(msg);
352
+ await this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
417
353
  }
418
- }, types_1.CONSUMER_DEFAULT_OPTIONS);
354
+ });
419
355
  if (!consumerTag) {
420
356
  logger_1.default.error(`rabbit: failed to consume from queue ${queue}`);
421
357
  }
422
358
  else {
423
359
  logger_1.default.info(`rabbit: adding tag ${consumerTag} to the array.`);
424
- this.consumersTags.push([confirmChannel, consumerTag]);
360
+ this.consumersTags.push([c, consumerTag]);
425
361
  }
426
362
  });
427
363
  }
@@ -430,9 +366,9 @@ class RabbitMq {
430
366
  RabbitMq.validateName('exchange', exchange);
431
367
  RabbitMq.validateName('queue', queue);
432
368
  const { limit, deadMessageTtl } = optionsWithDefaults;
433
- const channel = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
369
+ const channel = await this.getNewChannel();
434
370
  return channel.addSetup(async (c) => {
435
- const assertExchange = await utils_1.assertExchangeFanout(c, exchange);
371
+ const assertExchange = await (0, utils_1.assertExchangeFanout)(c, exchange);
436
372
  await c.assertQueue(queue);
437
373
  this.exchanges[exchange] = assertExchange;
438
374
  await c.prefetch(limit, true);
@@ -443,7 +379,7 @@ class RabbitMq {
443
379
  });
444
380
  }
445
381
  async publish(exchange, content, customHeaders) {
446
- return utils_1.wrapSetImmediate(async () => {
382
+ return (0, utils_1.wrapSetImmediate)(async () => {
447
383
  RabbitMq.validateName('exchange', exchange);
448
384
  const channel = await this.assertChannel();
449
385
  await this.assertExchange(exchange);
@@ -452,41 +388,21 @@ class RabbitMq {
452
388
  }
453
389
  async sendToQueue(queue, content, options, customHeaders, isBlocking) {
454
390
  const callback = async () => {
455
- try {
456
- RabbitMq.validateName('queue', queue);
457
- await this.assertChannel();
458
- await this.assertQueue(queue, options);
459
- const res = await this.channel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
460
- debug(`rabbit: sending to queue ${queue}`, { res });
461
- return res;
462
- }
463
- catch (e) {
464
- logger_1.default.error(`rabbit: failed to send to queue ${queue}`, { e });
465
- throw e;
466
- }
391
+ RabbitMq.validateName('queue', queue);
392
+ await this.assertChannel();
393
+ await this.assertQueue(queue, options);
394
+ const res = await this.channel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
395
+ debug(`rabbit: sending to queue ${queue}`, { res });
396
+ return res;
467
397
  };
468
398
  if (isBlocking) {
469
399
  return callback();
470
400
  }
471
- return utils_1.wrapSetImmediate(callback);
401
+ return (0, utils_1.wrapSetImmediate)(callback);
472
402
  }
473
403
  async isConnected() {
474
404
  const connection = await this.getConnection();
475
- const isConnected = connection.isConnected();
476
- if (!isConnected) {
477
- logger_1.default.error('rabbit: isConnected - false');
478
- return false;
479
- }
480
- const channel = await this.assertChannel();
481
- try {
482
- await channel.waitForConnect();
483
- }
484
- catch (e) {
485
- logger_1.default.error('rabbit: isConnected - false');
486
- return false;
487
- }
488
- logger_1.default.info('rabbit: isConnected - true');
489
- return true;
405
+ return connection.isConnected();
490
406
  }
491
407
  async gracefulShutdown(signal) {
492
408
  const tagsNumber = this.consumersTags.length;
@@ -2,17 +2,12 @@ export declare const DEFAULT_DEAD_TTL_TWO_DAYS: number;
2
2
  export declare const DEFAULT_LOCK_TIMEOUT: number;
3
3
  export declare const RETRY_HEADER = "x-retry-count";
4
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
5
  export declare const DEFAULT_USE_CONSUME_WITH_LOCK = false;
8
6
  export declare const CONNECTION_CREATED_CONST = "connectionCreated";
9
- export declare const CONNECTION_FAILED_CONST = "connectionFailed";
10
7
  export declare const DEFAULT_OPTIONS: {
11
8
  limit: number;
12
9
  retries: number;
13
10
  deadMessageTtl: number;
14
11
  lockTimeout: number;
15
12
  useConsumeWithLock: boolean;
16
- auditContext: null;
17
- enableRabbitTrace: boolean;
18
13
  };
@@ -1,21 +1,16 @@
1
1
  "use strict";
2
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;
3
+ exports.DEFAULT_OPTIONS = exports.CONNECTION_CREATED_CONST = exports.DEFAULT_USE_CONSUME_WITH_LOCK = exports.TRACING_HEADER = exports.RETRY_HEADER = exports.DEFAULT_LOCK_TIMEOUT = exports.DEFAULT_DEAD_TTL_TWO_DAYS = void 0;
4
4
  exports.DEFAULT_DEAD_TTL_TWO_DAYS = 60000 * 60 * 12;
5
5
  exports.DEFAULT_LOCK_TIMEOUT = 1000 * 5;
6
6
  exports.RETRY_HEADER = 'x-retry-count';
7
7
  exports.TRACING_HEADER = 'x-trace-id';
8
- exports.USER_TRACING_HEADER = 'x-af-user-id';
9
- exports.USER_OBJECT = 'userObject';
10
8
  exports.DEFAULT_USE_CONSUME_WITH_LOCK = false;
11
9
  exports.CONNECTION_CREATED_CONST = 'connectionCreated';
12
- exports.CONNECTION_FAILED_CONST = 'connectionFailed';
13
10
  exports.DEFAULT_OPTIONS = {
14
11
  limit: 1,
15
12
  retries: 1,
16
13
  deadMessageTtl: exports.DEFAULT_DEAD_TTL_TWO_DAYS,
17
14
  lockTimeout: exports.DEFAULT_LOCK_TIMEOUT,
18
15
  useConsumeWithLock: exports.DEFAULT_USE_CONSUME_WITH_LOCK,
19
- auditContext: null,
20
- enableRabbitTrace: false,
21
16
  };
@@ -1,4 +1,4 @@
1
- export declare type RedisConfig = {
1
+ export type RedisConfig = {
2
2
  host: string;
3
3
  port: number | undefined;
4
4
  prefix?: string;
@@ -1,15 +1,15 @@
1
- import { ConsumeMessage, Options } from 'amqplib';
1
+ import { ConsumeMessage } from 'amqplib';
2
2
  export interface ExchangesCache {
3
3
  [key: string]: any;
4
4
  }
5
5
  export interface QueuesCache {
6
6
  [key: string]: any;
7
7
  }
8
- export declare type CustomMessageHeaders = {
8
+ export type CustomMessageHeaders = {
9
9
  redisTimestampValidationKey?: string;
10
10
  };
11
- export declare type RedisLockType = (args0?: string, arg1?: number) => Promise<any>;
12
- export declare type ConsumeMessageOrNull = ConsumeMessage | null;
11
+ export type RedisLockType = (args0?: string, arg1?: number) => Promise<any>;
12
+ export type ConsumeMessageOrNull = ConsumeMessage | null;
13
13
  export interface ConsumeOptions {
14
14
  retries?: number;
15
15
  deadMessageTtl?: number;
@@ -17,21 +17,5 @@ export interface ConsumeOptions {
17
17
  limit?: number;
18
18
  lockTimeout?: number;
19
19
  useConsumeWithLock?: boolean;
20
- auditContext?: any;
21
- enableRabbitTrace?: boolean;
22
20
  }
23
- export declare type CallbackFunction = (msg: ConsumeMessage, ack: any, nack: any) => Promise<any>;
24
- export declare type newChannelOpts = {
25
- name?: string;
26
- onClose?: null | ((args: any | null) => void);
27
- };
28
- export declare type assertChannelOpts = {
29
- channelName?: string;
30
- force?: boolean;
31
- };
32
- export declare type AfConsumer = {
33
- queue: string;
34
- callback: CallbackFunction;
35
- options: ConsumeOptions | undefined;
36
- };
37
- export declare const CONSUMER_DEFAULT_OPTIONS: Options.Consume;
21
+ export type CallbackFunction = (msg: ConsumeMessage, ack: any, nack: any) => Promise<any>;
package/dist/lib/types.js CHANGED
@@ -1,11 +1,2 @@
1
1
  "use strict";
2
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
- };
@@ -2,4 +2,3 @@ import { ChannelWrapper } from 'amqp-connection-manager';
2
2
  import { ConfirmChannel } from 'amqplib';
3
3
  export declare const assertExchangeFanout: (c: ChannelWrapper | ConfirmChannel, exchangeName: string) => Promise<import("amqplib").Replies.AssertExchange>;
4
4
  export declare const wrapSetImmediate: (callback: () => any) => Promise<any>;
5
- export declare const rand: () => number;
package/dist/lib/utils.js CHANGED
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.rand = exports.wrapSetImmediate = exports.assertExchangeFanout = void 0;
4
- exports.assertExchangeFanout = async (c, exchangeName) => c.assertExchange(exchangeName, 'fanout');
5
- exports.wrapSetImmediate = (callback) => new Promise((resolve, reject) => {
3
+ 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) => {
6
7
  setImmediate(async () => {
7
8
  try {
8
9
  const value = await callback();
@@ -13,4 +14,4 @@ exports.wrapSetImmediate = (callback) => new Promise((resolve, reject) => {
13
14
  }
14
15
  });
15
16
  });
16
- exports.rand = () => Math.floor(Math.random() * 100000);
17
+ exports.wrapSetImmediate = wrapSetImmediate;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/rabbit",
3
- "version": "3.2.5",
3
+ "version": "3.2.6-testing",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
package/src/index.ts CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  import {
14
14
  getCurrentPayload, newTrace, traceTypes, createOrSetRabbitTrace, outbreak,
15
15
  } from '@autofleet/zehut';
16
+ import { v4 } from 'uuid';
16
17
  import logger from './logger';
17
18
  import RabbitError from './lib/rabbitError';
18
19
  import getRedisInstance, { RedisConfig } from './lib/redis';
@@ -170,6 +171,7 @@ class RabbitMq implements IAfRabbitMq {
170
171
  this.creatingConnection = false;
171
172
  this.exchanges = {};
172
173
  this.queues = {};
174
+ this.consumers = [];
173
175
  this.options = options;
174
176
  this.redisClient = redisConfig && getRedisInstance(redisConfig);
175
177
  if (this.redisClient) {
@@ -203,6 +205,7 @@ class RabbitMq implements IAfRabbitMq {
203
205
 
204
206
  public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage) : Promise<any> => {
205
207
  if (msg) {
208
+ debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
206
209
  await channel.ack(msg);
207
210
  const { properties: { headers } } = msg;
208
211
  const timestamp = headers?.creationTimestamp;
@@ -252,6 +255,7 @@ class RabbitMq implements IAfRabbitMq {
252
255
  : 1,
253
256
  });
254
257
  }
258
+ debug('rabbit nacking message', { deliveryTag: msg.fields.deliveryTag });
255
259
  await channel.ack(msg);
256
260
  } else {
257
261
  logger.error('no channel or msg', {
@@ -380,8 +384,7 @@ class RabbitMq implements IAfRabbitMq {
380
384
  }
381
385
 
382
386
  try {
383
- const channel = await this.getNewChannel({
384
- });
387
+ const channel = await this.getNewChannel({});
385
388
  channel.on('error', (err) => {
386
389
  logger.error('rabbit: channel error', { err });
387
390
  });
@@ -469,7 +472,8 @@ class RabbitMq implements IAfRabbitMq {
469
472
  }
470
473
 
471
474
  async consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
472
- return this.consumeFromRabbit(queue, callback, options);
475
+ await this.consumeFromRabbit(queue, callback, options);
476
+ return this.saveConsumer(queue, callback, options);
473
477
  }
474
478
 
475
479
  private async lockRedisIfNeeded(msg: any, options: any) {
@@ -495,6 +499,7 @@ class RabbitMq implements IAfRabbitMq {
495
499
  private async consumeFromRabbit(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
496
500
  const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
497
501
  RabbitMq.validateName('queue', queue);
502
+ const uniqueId = v4();
498
503
  const {
499
504
  limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace,
500
505
  } = optionsWithDefaults;
@@ -504,7 +509,7 @@ class RabbitMq implements IAfRabbitMq {
504
509
  }
505
510
  logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
506
511
  }
507
- const channel: ChannelWrapper = await this.getNewChannel({ name: `consume-queue-${queue}` });
512
+ const channel = await this.assertChannel();
508
513
  return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
509
514
  await confirmChannel.assertQueue(queue, options ? { messageTtl: options.messageTtl } : undefined);
510
515
  await confirmChannel.prefetch(limit, true);
@@ -557,6 +562,7 @@ class RabbitMq implements IAfRabbitMq {
557
562
  if (messageAcked) {
558
563
  return;
559
564
  }
565
+ debug('rabbit localAck', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
560
566
  messageAcked = true;
561
567
  return this.ack(confirmChannel, msg, true, releaseLock)(msg);
562
568
  };
@@ -565,6 +571,7 @@ class RabbitMq implements IAfRabbitMq {
565
571
  if (messageAcked) {
566
572
  return;
567
573
  }
574
+ debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
568
575
  messageAcked = true;
569
576
  return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
570
577
  };
@@ -660,7 +667,12 @@ class RabbitMq implements IAfRabbitMq {
660
667
  }
661
668
  const channel = await this.assertChannel();
662
669
  try {
663
- await channel.waitForConnect();
670
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
671
+ // @ts-ignore
672
+ await Promise.all([
673
+ channel.waitForConnect(),
674
+ ...this.consumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
675
+ ]);
664
676
  } catch (e) {
665
677
  logger.error('rabbit: isConnected - false');
666
678
  return false;