@autofleet/rabbit 2.3.6 → 2.3.7

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/src/index-new.ts DELETED
@@ -1,427 +0,0 @@
1
- /* eslint-disable @typescript-eslint/ban-ts-comment,@typescript-eslint/ban-types,@typescript-eslint/no-unused-vars,consistent-return,no-async-promise-executor,no-param-reassign,no-empty */
2
- // eslint-disable-next-line max-classes-per-file
3
- import moment from 'moment';
4
- import RedisLock from 'redis-lock';
5
- import { ConfirmChannel, Options, ConsumeMessage } from 'amqplib';
6
- import { AmqpConnectionManager, ChannelWrapper, connect } from 'amqp-connection-manager';
7
- import { EventEmitter } from 'events';
8
- // import { newTrace, traceTypes } from '@autofleet/outbreak';
9
- import { promisify } from 'util';
10
- import logger from './logger';
11
- import RabbitError from './rabbitError';
12
- import getRedisInstance, { RedisConfig } from './redis';
13
-
14
- export interface IAfRabbitMq {
15
- ack: any;
16
- nack: any;
17
- assertChannel: any;
18
- assertExchange: any;
19
- assertQueue: any;
20
- consume: any;
21
- consumeFromExchange: any;
22
- publish: any;
23
- sendToQueue: any;
24
- redisClient?: any;
25
- }
26
-
27
- interface ExchangesCache {
28
- [key: string]: any
29
- }
30
- interface QueuesCache {
31
- [key: string]: any
32
- }
33
-
34
- type CustomMessageHeaders = {
35
- redisTimestampValidationKey?: string;
36
- }
37
-
38
- type RedisLockType = (args0?: string, arg1?: number) => Promise<any>
39
-
40
- type ConsumeMessageOrNull = ConsumeMessage | null;
41
- interface ConsumeOptions {
42
- retries?: number;
43
- deadMessageTtl?: number;
44
- limit?: number;
45
- lockTimeout?: number;
46
- useConsumeWithLock?: boolean;
47
- }
48
-
49
- type CallbackFunction = (msg: ConsumeMessage, ack: Function, nack: Function) => Promise<any>
50
-
51
- export interface AfRabbitOptions {
52
- disableReconnect?: boolean;
53
- host?: string;
54
- }
55
-
56
- const DEFAULT_DEAD_TTL_TWO_DAYS = 60000 * 60 * 48;
57
- const DEFAULT_LOCK_TIMEOUT = 1000 * 5;
58
- const RETRY_HEADER = 'x-retry-count';
59
- const DEFAULT_USE_CONSUME_WITH_LOCK = false;
60
-
61
- const defaultOptions = {
62
- limit: 1,
63
- retries: 1,
64
- deadMessageTtl: DEFAULT_DEAD_TTL_TWO_DAYS,
65
- lockTimeout: DEFAULT_LOCK_TIMEOUT,
66
- useConsumeWithLock: DEFAULT_USE_CONSUME_WITH_LOCK,
67
- };
68
-
69
- const assertExchangeFanout = async (c: ConfirmChannel | ChannelWrapper, exchangeName: string) => c.assertExchange(exchangeName, 'fanout');
70
-
71
- const connectionCreatedConst = 'connectionCreated';
72
-
73
- const wrapSetImmediate = (callback: () => any) => new Promise<any>(async (resolve, reject) => {
74
- setImmediate(async () => {
75
- try {
76
- const value = await callback();
77
- resolve(value);
78
- } catch (error) {
79
- reject(error);
80
- }
81
- });
82
- });
83
-
84
- class RabbitMq implements IAfRabbitMq {
85
- static parseMsg(msg: any) : any {
86
- let { content } = msg;
87
- content = content.toString();
88
-
89
- try {
90
- content = JSON.parse(content);
91
- } catch (e) { }
92
-
93
- return {
94
- ...msg,
95
- content,
96
- };
97
- }
98
-
99
- static validateName(type: string, name: string) {
100
- if (!name || name === '') {
101
- throw new RabbitError(`error while using ${type} with no name`);
102
- }
103
- }
104
-
105
- static getPublishOptions(customHeaders: CustomMessageHeaders = {}) {
106
- return {
107
- timestamp: moment().unix(),
108
- headers: {
109
- creationTimestamp: moment().valueOf(),
110
- ...customHeaders,
111
- },
112
- };
113
- }
114
-
115
- PUBLISH_TIMEOUT = Number(process.env.RABBITMQ_PUBLISH_TIMEOUT) || 60000;
116
-
117
- PUBLISH_ERROR_MSG = `rabbit: publish timeout(${this.PUBLISH_TIMEOUT}ms) has pass, exchange: `;
118
-
119
- DISCONNECT_MSG = 'rabbit: connection disconnect';
120
-
121
- RESCONNECT_MSG = 'rabbit: reconnecting';
122
-
123
- channel: ChannelWrapper | null;
124
-
125
- connection: AmqpConnectionManager | null;
126
-
127
- em: EventEmitter;
128
-
129
- creatingConnection: boolean;
130
-
131
- exchanges: ExchangesCache;
132
-
133
- queues: QueuesCache;
134
-
135
- host: string | null;
136
-
137
- options: AfRabbitOptions | undefined;
138
-
139
- redisClient: any;
140
-
141
- redisLock?: RedisLockType;
142
-
143
- constructor(options?: AfRabbitOptions, redisConfig?: RedisConfig) {
144
- this.em = new EventEmitter();
145
- this.channel = null;
146
- this.connection = null;
147
- this.creatingConnection = false;
148
- this.exchanges = {};
149
- this.queues = {};
150
- this.options = options;
151
- this.host = options?.host ?? null;
152
- this.redisClient = redisConfig && getRedisInstance(redisConfig);
153
- if (this.redisClient) {
154
- this.redisLock = promisify(RedisLock(this.redisClient)) as RedisLockType;
155
- }
156
- }
157
-
158
- private shouldConsumeMessageByTimestamp = async (msg: ConsumeMessageOrNull) => {
159
- if (msg) {
160
- const { properties: { headers } } = msg;
161
- const timestamp = headers?.creationTimestamp;
162
-
163
- if (timestamp && headers?.redisTimestampValidationKey && this.redisClient) {
164
- const lastMessageTimestamp = await this.redisClient.getAsync(headers.redisTimestampValidationKey);
165
- return !lastMessageTimestamp || (parseInt(lastMessageTimestamp, 10) <= parseInt(timestamp, 10));
166
- }
167
- return true;
168
- }
169
- return false;
170
- }
171
-
172
- public ack = (channel: ChannelWrapper, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage) : Promise<any> => {
173
- if (msg) {
174
- await channel.ack(msg);
175
- const { properties: { headers } } = msg;
176
- const timestamp = headers?.creationTimestamp;
177
-
178
- if (shouldUpdateRedisTimestamp && timestamp && headers?.redisTimestampValidationKey && this.redisClient) {
179
- const parsedTimestamp = parseInt(timestamp, 10);
180
- await this.redisClient.setAsync(headers.redisTimestampValidationKey, parsedTimestamp, 'EX', 3600);
181
- await this.unlockRedisIfNeeded(releaseLock);
182
- }
183
- }
184
- }
185
-
186
- public nack = (
187
- channel: ChannelWrapper,
188
- queue: string,
189
- options: any,
190
- deadQueueOptions: Options.AssertQueue,
191
- msg: ConsumeMessageOrNull,
192
- releaseLock: any,
193
- ) => async (
194
- userMsg: ConsumeMessageOrNull,
195
- {
196
- skipRetry = false,
197
- }: any = { },
198
- ) : Promise<any> => {
199
- await this.unlockRedisIfNeeded(releaseLock);
200
- if (channel && msg) {
201
- if (
202
- !skipRetry
203
- && (
204
- !msg.properties.headers[RETRY_HEADER]
205
- || parseInt(msg.properties.headers[RETRY_HEADER], 10) < options.retries
206
- )
207
- ) {
208
- await this.sendToQueue(queue, RabbitMq.parseMsg(msg).content, options, {
209
- ...msg.properties.headers,
210
- [RETRY_HEADER]: msg.properties.headers[RETRY_HEADER]
211
- ? msg.properties.headers[RETRY_HEADER] + 1
212
- : 1,
213
- });
214
- } else {
215
- const deadQueue = `${queue}-dead`;
216
- await this.sendToQueue(deadQueue, RabbitMq.parseMsg(msg).content, deadQueueOptions, {
217
- ...msg.properties.headers,
218
- [RETRY_HEADER]: msg.properties.headers[RETRY_HEADER]
219
- ? msg.properties.headers[RETRY_HEADER] + 1
220
- : 1,
221
- });
222
- }
223
- await channel.ack(msg);
224
- }
225
- }
226
-
227
- async getConnection() {
228
- return new Promise<AmqpConnectionManager>(async (resolve) => {
229
- if (this.connection !== null) {
230
- return resolve(this.connection);
231
- }
232
- if (this.creatingConnection) {
233
- this.em.once(connectionCreatedConst, resolve);
234
- return;
235
- }
236
- this.creatingConnection = true;
237
- logger.info('rabbit: env username', { userName: (process.env.RABBITMQ_USERNAME || 'doesnt exist') });
238
- const username: string = process.env.RABBITMQ_USERNAME || 'guest';
239
- const password: string = process.env.RABBITMQ_PASSWORD || 'guest';
240
- const host: string = this.host || process.env.RABBITMQ_SERVICE_HOST || '';
241
- const connection: AmqpConnectionManager = await connect([`amqp://${username}:${password}@${host}`], { reconnectTimeInSeconds: 0.5 });
242
- this.connection = connection;
243
- this.creatingConnection = false;
244
- this.em.emit(connectionCreatedConst, connection);
245
- resolve(connection);
246
- });
247
- }
248
-
249
- async getNewChannel() {
250
- const connection: AmqpConnectionManager = await this.getConnection();
251
- return connection.createChannel({});
252
- }
253
-
254
- async assertChannel() {
255
- return new Promise<ChannelWrapper>(async (resolve, reject) => {
256
- if (this.channel) {
257
- return resolve(this.channel);
258
- }
259
-
260
- try {
261
- const connection: AmqpConnectionManager = await this.getConnection();
262
- if (this.channel === null) {
263
- this.channel = await connection.createChannel({});
264
- }
265
- resolve(this.channel);
266
- } catch (e) {
267
- reject(e);
268
- }
269
- });
270
- }
271
-
272
- async assertExchange(exchangeName: string, options?: any) {
273
- const channel: ChannelWrapper = await this.assertChannel();
274
- if (this.exchanges[exchangeName]) {
275
- return this.exchanges[exchangeName];
276
- }
277
- const exchange = await assertExchangeFanout(channel, exchangeName);
278
- this.exchanges[exchangeName] = exchange;
279
- return exchange;
280
- }
281
-
282
- async getQueueLength(queue: string) {
283
- RabbitMq.validateName('queue', queue);
284
- const channel: ChannelWrapper = await this.assertChannel();
285
- // @ts-ignore
286
- return channel.checkQueue(queue);
287
- }
288
-
289
- async bindQueue(queue: string, exchange: string) {
290
- const channel: ChannelWrapper = await this.assertChannel();
291
- await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.bindQueue(queue, exchange, ''));
292
- // @ts-ignore
293
- return channel.bindQueue(queue, exchange, '');
294
- }
295
-
296
- async assertQueue(queueName: string, options?: Options.AssertQueue) {
297
- RabbitMq.validateName('queue', queueName);
298
- const channel: ChannelWrapper = await this.assertChannel();
299
- if (this.queues[queueName]) {
300
- return this.queues[queueName];
301
- }
302
- await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, options));
303
- const queue = await channel.assertQueue(queueName, options);
304
- this.queues[queueName] = queueName;
305
- return queue;
306
- }
307
-
308
- async consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
309
- return this.consumeFromRabbit(queue, callback, options);
310
- }
311
-
312
- private async lockRedisIfNeeded(msg: any, options: any) {
313
- const { properties: { headers } } = msg;
314
- const timestamp = headers?.creationTimestamp;
315
- let releaseLock = null;
316
-
317
- if (options.useConsumeWithLock && timestamp && headers?.redisTimestampValidationKey && this.redisLock) {
318
- releaseLock = await this.redisLock(
319
- headers.redisTimestampValidationKey,
320
- options?.lockTimeout || DEFAULT_LOCK_TIMEOUT,
321
- );
322
- }
323
- return releaseLock;
324
- }
325
-
326
- private async unlockRedisIfNeeded(releaseLock: any) {
327
- if (this.redisLock && releaseLock) {
328
- await releaseLock();
329
- }
330
- }
331
-
332
- private async consumeFromRabbit(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
333
- const optionsWithDefaults = { ...defaultOptions, ...options };
334
- RabbitMq.validateName('queue', queue);
335
- const {
336
- limit, deadMessageTtl, useConsumeWithLock, lockTimeout,
337
- } = optionsWithDefaults;
338
- if (useConsumeWithLock) {
339
- if (!this.redisLock) {
340
- throw new Error('Usage of consumeWithLock requires RedisInstance');
341
- }
342
- logger.info(`Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
343
- }
344
- const channel: ChannelWrapper = await this.getNewChannel();
345
- return channel.addSetup(async (c: ConfirmChannel) => {
346
- await c.assertQueue(queue);
347
- await c.prefetch(limit, true);
348
- return Promise.all([
349
- c.consume(
350
- queue,
351
- async (msg: ConsumeMessageOrNull) => {
352
- if (!msg) {
353
- return null;
354
- }
355
- const parsedMessage = RabbitMq.parseMsg(msg);
356
- const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
357
- const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
358
- if (!shouldConsume) {
359
- await this.unlockRedisIfNeeded(releaseLock);
360
- return this.ack(channel, msg)(msg);
361
- }
362
- try {
363
- await callback(
364
- parsedMessage,
365
- this.ack(channel, msg, true, releaseLock),
366
- this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock),
367
- );
368
- } catch (e) {
369
- await this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
370
- }
371
- },
372
- ),
373
- ]);
374
- });
375
- }
376
-
377
- async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions) : Promise<any> {
378
- const optionsWithDefaults = { ...defaultOptions, ...options };
379
- RabbitMq.validateName('exchange', exchange);
380
- RabbitMq.validateName('queue', queue);
381
- const { limit, deadMessageTtl } = optionsWithDefaults;
382
- const channel: ChannelWrapper = await this.getNewChannel();
383
-
384
- return channel.addSetup(async (c: ConfirmChannel) => {
385
- await assertExchangeFanout(c, exchange);
386
- await c.assertQueue(queue);
387
- await c.prefetch(limit, true);
388
- return Promise.all([
389
- c.bindQueue(queue, exchange, ''),
390
- this.consume(
391
- queue,
392
- callback,
393
- options,
394
- ),
395
- ]);
396
- });
397
- }
398
-
399
- async publish(exchange: string, content: any, customHeaders?: any) : Promise<boolean> {
400
- return wrapSetImmediate(async () => {
401
- RabbitMq.validateName('exchange', exchange);
402
- const channel: ChannelWrapper = await this.assertChannel();
403
- await this.assertExchange(exchange);
404
- await channel.publish(exchange, '',
405
- Buffer.from(JSON.stringify(content)),
406
- RabbitMq.getPublishOptions(customHeaders));
407
- });
408
- }
409
-
410
- async sendToQueue(queue: string, content: any, options?: any, customHeaders?: any) : Promise<boolean> {
411
- return wrapSetImmediate(async () => {
412
- RabbitMq.validateName('queue', queue);
413
- const channel: ChannelWrapper = await this.assertChannel();
414
- await this.assertQueue(queue, options);
415
- return channel.sendToQueue(queue,
416
- Buffer.from(JSON.stringify(content)),
417
- RabbitMq.getPublishOptions(customHeaders));
418
- });
419
- }
420
-
421
- async isConnected() : Promise<boolean> {
422
- const connection = await this.getConnection();
423
- return connection.isConnected();
424
- }
425
- }
426
-
427
- export default RabbitMq;
@@ -1,44 +0,0 @@
1
- /* eslint-disable no-await-in-loop */
2
- // eslint-disable-next-line import/no-extraneous-dependencies
3
- import proxy from 'node-tcp-proxy';
4
- import RabbitMq from './index-new';
5
-
6
- const delay = (ms = 1000) => new Promise((resolve) => setTimeout(() => resolve(), ms));
7
- const callback = (eq: any, timeout = 0) => async (msg: any, ack: any, nack: any) => {
8
- setTimeout(async () => {
9
- // console.log(msg)
10
- await ack(msg);
11
- }, timeout);
12
- return null;
13
- };
14
-
15
- const payload = { ttt: 123 };
16
- const testId = 'www';
17
-
18
- const main = async () => {
19
- const port = 6672;
20
- const createProxy = () => proxy.createProxy(port, '0.0.0.0', 5672);
21
- const restartProxy = async (currentProxy, sleep) => {
22
- currentProxy.end();
23
- await delay(sleep);
24
- return proxy.createProxy(port, '0.0.0.0', 5672);
25
- };
26
- let currentProxy = createProxy();
27
- const rabbit = new RabbitMq({ host: `127.0.0.1:${port}` });
28
- const queue = `test-timestamp-validation-${testId}`;
29
- const mockFn = callback(payload);
30
- await rabbit.consume(queue, mockFn);
31
- console.error('start');
32
- const usedB = process.memoryUsage().heapUsed / 1024 / 1024;
33
- console.log('Used before', usedB);
34
- for (let i = 0; i < 10000; i += 1) {
35
- // currentProxy = await restartProxy(currentProxy, 100);
36
- await rabbit.sendToQueue(queue, payload);
37
- await delay(10);
38
- // expect(mockFn).toBeCalledTimes(i);
39
- const used = process.memoryUsage().heapUsed / 1024 / 1024;
40
- console.error('Used', i, used);
41
- }
42
- };
43
-
44
- main();