@autofleet/rabbit 2.3.4 → 2.3.6

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 @@
1
+ RABBITMQ_SERVICE_HOST=127.0.0.1:5673
@@ -0,0 +1,85 @@
1
+ /// <reference types="node" />
2
+ import { Options, ConsumeMessage } from 'amqplib';
3
+ import { AmqpConnectionManager, ChannelWrapper } from 'amqp-connection-manager';
4
+ import { EventEmitter } from 'events';
5
+ import { RedisConfig } from './redis';
6
+ export interface IAfRabbitMq {
7
+ ack: any;
8
+ nack: any;
9
+ assertChannel: any;
10
+ assertExchange: any;
11
+ assertQueue: any;
12
+ consume: any;
13
+ consumeFromExchange: any;
14
+ publish: any;
15
+ sendToQueue: any;
16
+ redisClient?: any;
17
+ }
18
+ interface ExchangesCache {
19
+ [key: string]: any;
20
+ }
21
+ interface QueuesCache {
22
+ [key: string]: any;
23
+ }
24
+ declare type CustomMessageHeaders = {
25
+ redisTimestampValidationKey?: string;
26
+ };
27
+ declare type RedisLockType = (args0?: string, arg1?: number) => Promise<any>;
28
+ declare type ConsumeMessageOrNull = ConsumeMessage | null;
29
+ interface ConsumeOptions {
30
+ retries?: number;
31
+ deadMessageTtl?: number;
32
+ limit?: number;
33
+ lockTimeout?: number;
34
+ useConsumeWithLock?: boolean;
35
+ }
36
+ declare type CallbackFunction = (msg: ConsumeMessage, ack: Function, nack: Function) => Promise<any>;
37
+ export interface AfRabbitOptions {
38
+ disableReconnect?: boolean;
39
+ host?: string;
40
+ }
41
+ declare class RabbitMq implements IAfRabbitMq {
42
+ static parseMsg(msg: any): any;
43
+ static validateName(type: string, name: string): void;
44
+ static getPublishOptions(customHeaders?: CustomMessageHeaders): {
45
+ timestamp: number;
46
+ headers: {
47
+ redisTimestampValidationKey?: string | undefined;
48
+ creationTimestamp: number;
49
+ };
50
+ };
51
+ PUBLISH_TIMEOUT: number;
52
+ PUBLISH_ERROR_MSG: string;
53
+ DISCONNECT_MSG: string;
54
+ RESCONNECT_MSG: string;
55
+ channel: ChannelWrapper | null;
56
+ connection: AmqpConnectionManager | null;
57
+ em: EventEmitter;
58
+ creatingConnection: boolean;
59
+ exchanges: ExchangesCache;
60
+ queues: QueuesCache;
61
+ host: string | null;
62
+ options: AfRabbitOptions | undefined;
63
+ redisClient: any;
64
+ redisLock?: RedisLockType;
65
+ constructor(options?: AfRabbitOptions, redisConfig?: RedisConfig);
66
+ private shouldConsumeMessageByTimestamp;
67
+ ack: (channel: ChannelWrapper, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp?: boolean, releaseLock?: null) => (userMsg: ConsumeMessage) => Promise<any>;
68
+ nack: (channel: ChannelWrapper, queue: string, options: any, deadQueueOptions: Options.AssertQueue, msg: ConsumeMessageOrNull, releaseLock: any) => (userMsg: ConsumeMessageOrNull, { skipRetry, }?: any) => Promise<any>;
69
+ getConnection(): Promise<AmqpConnectionManager>;
70
+ getNewChannel(): Promise<ChannelWrapper>;
71
+ assertChannel(): Promise<ChannelWrapper>;
72
+ assertExchange(exchangeName: string, options?: any): Promise<any>;
73
+ getQueueLength(queue: string): Promise<import("amqplib").Replies.AssertQueue>;
74
+ bindQueue(queue: string, exchange: string): Promise<void>;
75
+ assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any>;
76
+ consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any>;
77
+ private lockRedisIfNeeded;
78
+ private unlockRedisIfNeeded;
79
+ private consumeFromRabbit;
80
+ consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any>;
81
+ publish(exchange: string, content: any, customHeaders?: any): Promise<boolean>;
82
+ sendToQueue(queue: string, content: any, options?: any, customHeaders?: any): Promise<boolean>;
83
+ isConnected(): Promise<boolean>;
84
+ }
85
+ export default RabbitMq;
@@ -0,0 +1,327 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ /* 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 */
16
+ // eslint-disable-next-line max-classes-per-file
17
+ const moment_1 = __importDefault(require("moment"));
18
+ const redis_lock_1 = __importDefault(require("redis-lock"));
19
+ const amqp_connection_manager_1 = require("amqp-connection-manager");
20
+ const events_1 = require("events");
21
+ // import { newTrace, traceTypes } from '@autofleet/outbreak';
22
+ const util_1 = require("util");
23
+ const logger_1 = __importDefault(require("./logger"));
24
+ const rabbitError_1 = __importDefault(require("./rabbitError"));
25
+ const redis_1 = __importDefault(require("./redis"));
26
+ const DEFAULT_DEAD_TTL_TWO_DAYS = 60000 * 60 * 48;
27
+ const DEFAULT_LOCK_TIMEOUT = 1000 * 5;
28
+ const RETRY_HEADER = 'x-retry-count';
29
+ const DEFAULT_USE_CONSUME_WITH_LOCK = false;
30
+ const defaultOptions = {
31
+ limit: 1,
32
+ retries: 1,
33
+ deadMessageTtl: DEFAULT_DEAD_TTL_TWO_DAYS,
34
+ lockTimeout: DEFAULT_LOCK_TIMEOUT,
35
+ useConsumeWithLock: DEFAULT_USE_CONSUME_WITH_LOCK,
36
+ };
37
+ const assertExchangeFanout = (c, exchangeName) => __awaiter(void 0, void 0, void 0, function* () { return c.assertExchange(exchangeName, 'fanout'); });
38
+ const connectionCreatedConst = 'connectionCreated';
39
+ const wrapSetImmediate = (callback) => new Promise((resolve, reject) => __awaiter(void 0, void 0, void 0, function* () {
40
+ setImmediate(() => __awaiter(void 0, void 0, void 0, function* () {
41
+ try {
42
+ const value = yield callback();
43
+ resolve(value);
44
+ }
45
+ catch (error) {
46
+ reject(error);
47
+ }
48
+ }));
49
+ }));
50
+ class RabbitMq {
51
+ constructor(options, redisConfig) {
52
+ var _a;
53
+ this.PUBLISH_TIMEOUT = Number(process.env.RABBITMQ_PUBLISH_TIMEOUT) || 60000;
54
+ this.PUBLISH_ERROR_MSG = `rabbit: publish timeout(${this.PUBLISH_TIMEOUT}ms) has pass, exchange: `;
55
+ this.DISCONNECT_MSG = 'rabbit: connection disconnect';
56
+ this.RESCONNECT_MSG = 'rabbit: reconnecting';
57
+ this.shouldConsumeMessageByTimestamp = (msg) => __awaiter(this, void 0, void 0, function* () {
58
+ if (msg) {
59
+ const { properties: { headers } } = msg;
60
+ const timestamp = headers === null || headers === void 0 ? void 0 : headers.creationTimestamp;
61
+ if (timestamp && (headers === null || headers === void 0 ? void 0 : headers.redisTimestampValidationKey) && this.redisClient) {
62
+ const lastMessageTimestamp = yield this.redisClient.getAsync(headers.redisTimestampValidationKey);
63
+ return !lastMessageTimestamp || (parseInt(lastMessageTimestamp, 10) <= parseInt(timestamp, 10));
64
+ }
65
+ return true;
66
+ }
67
+ return false;
68
+ });
69
+ this.ack = (channel, msg, shouldUpdateRedisTimestamp = false, releaseLock = null) => (userMsg) => __awaiter(this, void 0, void 0, function* () {
70
+ if (msg) {
71
+ yield channel.ack(msg);
72
+ const { properties: { headers } } = msg;
73
+ const timestamp = headers === null || headers === void 0 ? void 0 : headers.creationTimestamp;
74
+ if (shouldUpdateRedisTimestamp && timestamp && (headers === null || headers === void 0 ? void 0 : headers.redisTimestampValidationKey) && this.redisClient) {
75
+ const parsedTimestamp = parseInt(timestamp, 10);
76
+ yield this.redisClient.setAsync(headers.redisTimestampValidationKey, parsedTimestamp, 'EX', 3600);
77
+ yield this.unlockRedisIfNeeded(releaseLock);
78
+ }
79
+ }
80
+ });
81
+ this.nack = (channel, queue, options, deadQueueOptions, msg, releaseLock) => (userMsg, { skipRetry = false, } = {}) => __awaiter(this, void 0, void 0, function* () {
82
+ yield this.unlockRedisIfNeeded(releaseLock);
83
+ if (channel && msg) {
84
+ if (!skipRetry
85
+ && (!msg.properties.headers[RETRY_HEADER]
86
+ || parseInt(msg.properties.headers[RETRY_HEADER], 10) < options.retries)) {
87
+ yield this.sendToQueue(queue, RabbitMq.parseMsg(msg).content, options, Object.assign(Object.assign({}, msg.properties.headers), { [RETRY_HEADER]: msg.properties.headers[RETRY_HEADER]
88
+ ? msg.properties.headers[RETRY_HEADER] + 1
89
+ : 1 }));
90
+ }
91
+ else {
92
+ const deadQueue = `${queue}-dead`;
93
+ yield this.sendToQueue(deadQueue, RabbitMq.parseMsg(msg).content, deadQueueOptions, Object.assign(Object.assign({}, msg.properties.headers), { [RETRY_HEADER]: msg.properties.headers[RETRY_HEADER]
94
+ ? msg.properties.headers[RETRY_HEADER] + 1
95
+ : 1 }));
96
+ }
97
+ yield channel.ack(msg);
98
+ }
99
+ });
100
+ this.em = new events_1.EventEmitter();
101
+ this.channel = null;
102
+ this.connection = null;
103
+ this.creatingConnection = false;
104
+ this.exchanges = {};
105
+ this.queues = {};
106
+ this.options = options;
107
+ this.host = (_a = options === null || options === void 0 ? void 0 : options.host) !== null && _a !== void 0 ? _a : null;
108
+ this.redisClient = redisConfig && redis_1.default(redisConfig);
109
+ if (this.redisClient) {
110
+ this.redisLock = util_1.promisify(redis_lock_1.default(this.redisClient));
111
+ }
112
+ }
113
+ static parseMsg(msg) {
114
+ let { content } = msg;
115
+ content = content.toString();
116
+ try {
117
+ content = JSON.parse(content);
118
+ }
119
+ catch (e) { }
120
+ return Object.assign(Object.assign({}, msg), { content });
121
+ }
122
+ static validateName(type, name) {
123
+ if (!name || name === '') {
124
+ throw new rabbitError_1.default(`error while using ${type} with no name`);
125
+ }
126
+ }
127
+ static getPublishOptions(customHeaders = {}) {
128
+ return {
129
+ timestamp: moment_1.default().unix(),
130
+ headers: Object.assign({ creationTimestamp: moment_1.default().valueOf() }, customHeaders),
131
+ };
132
+ }
133
+ getConnection() {
134
+ return __awaiter(this, void 0, void 0, function* () {
135
+ return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
136
+ if (this.connection !== null) {
137
+ return resolve(this.connection);
138
+ }
139
+ if (this.creatingConnection) {
140
+ this.em.once(connectionCreatedConst, resolve);
141
+ return;
142
+ }
143
+ this.creatingConnection = true;
144
+ logger_1.default.info('rabbit: env username', { userName: (process.env.RABBITMQ_USERNAME || 'doesnt exist') });
145
+ const username = process.env.RABBITMQ_USERNAME || 'guest';
146
+ const password = process.env.RABBITMQ_PASSWORD || 'guest';
147
+ const host = this.host || process.env.RABBITMQ_SERVICE_HOST || '';
148
+ const connection = yield amqp_connection_manager_1.connect([`amqp://${username}:${password}@${host}`], { reconnectTimeInSeconds: 0.5 });
149
+ this.connection = connection;
150
+ this.creatingConnection = false;
151
+ this.em.emit(connectionCreatedConst, connection);
152
+ resolve(connection);
153
+ }));
154
+ });
155
+ }
156
+ getNewChannel() {
157
+ return __awaiter(this, void 0, void 0, function* () {
158
+ const connection = yield this.getConnection();
159
+ return connection.createChannel({});
160
+ });
161
+ }
162
+ assertChannel() {
163
+ return __awaiter(this, void 0, void 0, function* () {
164
+ return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
165
+ if (this.channel) {
166
+ return resolve(this.channel);
167
+ }
168
+ try {
169
+ const connection = yield this.getConnection();
170
+ if (this.channel === null) {
171
+ this.channel = yield connection.createChannel({});
172
+ }
173
+ resolve(this.channel);
174
+ }
175
+ catch (e) {
176
+ reject(e);
177
+ }
178
+ }));
179
+ });
180
+ }
181
+ assertExchange(exchangeName, options) {
182
+ return __awaiter(this, void 0, void 0, function* () {
183
+ const channel = yield this.assertChannel();
184
+ if (this.exchanges[exchangeName]) {
185
+ return this.exchanges[exchangeName];
186
+ }
187
+ const exchange = yield assertExchangeFanout(channel, exchangeName);
188
+ this.exchanges[exchangeName] = exchange;
189
+ return exchange;
190
+ });
191
+ }
192
+ getQueueLength(queue) {
193
+ return __awaiter(this, void 0, void 0, function* () {
194
+ RabbitMq.validateName('queue', queue);
195
+ const channel = yield this.assertChannel();
196
+ // @ts-ignore
197
+ return channel.checkQueue(queue);
198
+ });
199
+ }
200
+ bindQueue(queue, exchange) {
201
+ return __awaiter(this, void 0, void 0, function* () {
202
+ const channel = yield this.assertChannel();
203
+ yield channel.addSetup((setupChannel) => setupChannel.bindQueue(queue, exchange, ''));
204
+ // @ts-ignore
205
+ return channel.bindQueue(queue, exchange, '');
206
+ });
207
+ }
208
+ assertQueue(queueName, options) {
209
+ return __awaiter(this, void 0, void 0, function* () {
210
+ RabbitMq.validateName('queue', queueName);
211
+ const channel = yield this.assertChannel();
212
+ if (this.queues[queueName]) {
213
+ return this.queues[queueName];
214
+ }
215
+ yield channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, options));
216
+ const queue = yield channel.assertQueue(queueName, options);
217
+ this.queues[queueName] = queueName;
218
+ return queue;
219
+ });
220
+ }
221
+ consume(queue, callback, options) {
222
+ return __awaiter(this, void 0, void 0, function* () {
223
+ return this.consumeFromRabbit(queue, callback, options);
224
+ });
225
+ }
226
+ lockRedisIfNeeded(msg, options) {
227
+ return __awaiter(this, void 0, void 0, function* () {
228
+ const { properties: { headers } } = msg;
229
+ const timestamp = headers === null || headers === void 0 ? void 0 : headers.creationTimestamp;
230
+ let releaseLock = null;
231
+ if (options.useConsumeWithLock && timestamp && (headers === null || headers === void 0 ? void 0 : headers.redisTimestampValidationKey) && this.redisLock) {
232
+ releaseLock = yield this.redisLock(headers.redisTimestampValidationKey, (options === null || options === void 0 ? void 0 : options.lockTimeout) || DEFAULT_LOCK_TIMEOUT);
233
+ }
234
+ return releaseLock;
235
+ });
236
+ }
237
+ unlockRedisIfNeeded(releaseLock) {
238
+ return __awaiter(this, void 0, void 0, function* () {
239
+ if (this.redisLock && releaseLock) {
240
+ yield releaseLock();
241
+ }
242
+ });
243
+ }
244
+ consumeFromRabbit(queue, callback, options) {
245
+ return __awaiter(this, void 0, void 0, function* () {
246
+ const optionsWithDefaults = Object.assign(Object.assign({}, defaultOptions), options);
247
+ RabbitMq.validateName('queue', queue);
248
+ const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, } = optionsWithDefaults;
249
+ if (useConsumeWithLock) {
250
+ if (!this.redisLock) {
251
+ throw new Error('Usage of consumeWithLock requires RedisInstance');
252
+ }
253
+ logger_1.default.info(`Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
254
+ }
255
+ const channel = yield this.getNewChannel();
256
+ return channel.addSetup((c) => __awaiter(this, void 0, void 0, function* () {
257
+ yield c.assertQueue(queue);
258
+ yield c.prefetch(limit, true);
259
+ return Promise.all([
260
+ c.consume(queue, (msg) => __awaiter(this, void 0, void 0, function* () {
261
+ if (!msg) {
262
+ return null;
263
+ }
264
+ const parsedMessage = RabbitMq.parseMsg(msg);
265
+ const releaseLock = yield this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
266
+ const shouldConsume = yield this.shouldConsumeMessageByTimestamp(parsedMessage);
267
+ if (!shouldConsume) {
268
+ yield this.unlockRedisIfNeeded(releaseLock);
269
+ return this.ack(channel, msg)(msg);
270
+ }
271
+ try {
272
+ yield callback(parsedMessage, this.ack(channel, msg, true, releaseLock), this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock));
273
+ }
274
+ catch (e) {
275
+ yield this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
276
+ }
277
+ })),
278
+ ]);
279
+ }));
280
+ });
281
+ }
282
+ consumeFromExchange(queue, exchange, callback, options) {
283
+ return __awaiter(this, void 0, void 0, function* () {
284
+ const optionsWithDefaults = Object.assign(Object.assign({}, defaultOptions), options);
285
+ RabbitMq.validateName('exchange', exchange);
286
+ RabbitMq.validateName('queue', queue);
287
+ const { limit, deadMessageTtl } = optionsWithDefaults;
288
+ const channel = yield this.getNewChannel();
289
+ return channel.addSetup((c) => __awaiter(this, void 0, void 0, function* () {
290
+ yield assertExchangeFanout(c, exchange);
291
+ yield c.assertQueue(queue);
292
+ yield c.prefetch(limit, true);
293
+ return Promise.all([
294
+ c.bindQueue(queue, exchange, ''),
295
+ this.consume(queue, callback, options),
296
+ ]);
297
+ }));
298
+ });
299
+ }
300
+ publish(exchange, content, customHeaders) {
301
+ return __awaiter(this, void 0, void 0, function* () {
302
+ return wrapSetImmediate(() => __awaiter(this, void 0, void 0, function* () {
303
+ RabbitMq.validateName('exchange', exchange);
304
+ const channel = yield this.assertChannel();
305
+ yield this.assertExchange(exchange);
306
+ yield channel.publish(exchange, '', Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
307
+ }));
308
+ });
309
+ }
310
+ sendToQueue(queue, content, options, customHeaders) {
311
+ return __awaiter(this, void 0, void 0, function* () {
312
+ return wrapSetImmediate(() => __awaiter(this, void 0, void 0, function* () {
313
+ RabbitMq.validateName('queue', queue);
314
+ const channel = yield this.assertChannel();
315
+ yield this.assertQueue(queue, options);
316
+ return channel.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
317
+ }));
318
+ });
319
+ }
320
+ isConnected() {
321
+ return __awaiter(this, void 0, void 0, function* () {
322
+ const connection = yield this.getConnection();
323
+ return connection.isConnected();
324
+ });
325
+ }
326
+ }
327
+ exports.default = RabbitMq;
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ const defaultOptions = {
34
34
  lockTimeout: DEFAULT_LOCK_TIMEOUT,
35
35
  useConsumeWithLock: DEFAULT_USE_CONSUME_WITH_LOCK,
36
36
  };
37
- const assertExchangeFanout = (c, exchangeName) => c.assertExchange(exchangeName, 'fanout');
37
+ const assertExchangeFanout = (c, exchangeName) => __awaiter(void 0, void 0, void 0, function* () { return c.assertExchange(exchangeName, 'fanout'); });
38
38
  const connectionCreatedConst = 'connectionCreated';
39
39
  const wrapSetImmediate = (callback) => new Promise((resolve, reject) => __awaiter(void 0, void 0, void 0, function* () {
40
40
  setImmediate(() => __awaiter(void 0, void 0, void 0, function* () {
@@ -178,7 +178,7 @@ class RabbitMq {
178
178
  try {
179
179
  const connection = yield this.getConnection();
180
180
  if (this.channel === null) {
181
- this.channel = connection.createChannel({});
181
+ this.channel = yield connection.createChannel({});
182
182
  }
183
183
  resolve(this.channel);
184
184
  }
@@ -204,12 +204,13 @@ class RabbitMq {
204
204
  RabbitMq.validateName('queue', queue);
205
205
  const channel = yield this.assertChannel();
206
206
  // @ts-ignore
207
- return channel.checkQueue(queue);
207
+ return channel.assertQueue(queue);
208
208
  });
209
209
  }
210
210
  bindQueue(queue, exchange) {
211
211
  return __awaiter(this, void 0, void 0, function* () {
212
212
  const channel = yield this.assertChannel();
213
+ yield channel.addSetup((setupChannel) => setupChannel.bindQueue(queue, exchange, ''));
213
214
  // @ts-ignore
214
215
  return channel.bindQueue(queue, exchange, '');
215
216
  });
@@ -221,6 +222,7 @@ class RabbitMq {
221
222
  if (this.queues[queueName]) {
222
223
  return this.queues[queueName];
223
224
  }
225
+ yield channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, options));
224
226
  const queue = yield channel.assertQueue(queueName, options);
225
227
  this.queues[queueName] = queueName;
226
228
  return queue;
@@ -262,6 +264,7 @@ class RabbitMq {
262
264
  }
263
265
  const channel = yield this.getNewChannel();
264
266
  return channel.addSetup((c) => __awaiter(this, void 0, void 0, function* () {
267
+ console.log('addSetup');
265
268
  yield c.assertQueue(queue);
266
269
  yield c.prefetch(limit, true);
267
270
  return Promise.all([
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ /* eslint-disable no-await-in-loop */
16
+ // eslint-disable-next-line import/no-extraneous-dependencies
17
+ const node_tcp_proxy_1 = __importDefault(require("node-tcp-proxy"));
18
+ const index_new_1 = __importDefault(require("./index-new"));
19
+ const delay = (ms = 1000) => new Promise((resolve) => setTimeout(() => resolve(), ms));
20
+ const callback = (eq, timeout = 0) => (msg, ack, nack) => __awaiter(void 0, void 0, void 0, function* () {
21
+ setTimeout(() => __awaiter(void 0, void 0, void 0, function* () {
22
+ // console.log(msg)
23
+ yield ack(msg);
24
+ }), timeout);
25
+ return null;
26
+ });
27
+ const payload = { ttt: 123 };
28
+ const testId = 'www';
29
+ const main = () => __awaiter(void 0, void 0, void 0, function* () {
30
+ const port = 6672;
31
+ const createProxy = () => node_tcp_proxy_1.default.createProxy(port, '0.0.0.0', 5672);
32
+ const restartProxy = (currentProxy, sleep) => __awaiter(void 0, void 0, void 0, function* () {
33
+ currentProxy.end();
34
+ yield delay(sleep);
35
+ return node_tcp_proxy_1.default.createProxy(port, '0.0.0.0', 5672);
36
+ });
37
+ let currentProxy = createProxy();
38
+ const rabbit = new index_new_1.default({ host: `127.0.0.1:${port}` });
39
+ const queue = `test-timestamp-validation-${testId}`;
40
+ const mockFn = callback(payload);
41
+ yield rabbit.consume(queue, mockFn);
42
+ console.error('start');
43
+ const usedB = process.memoryUsage().heapUsed / 1024 / 1024;
44
+ console.log('Used before', usedB);
45
+ for (let i = 0; i < 10000; i += 1) {
46
+ // currentProxy = await restartProxy(currentProxy, 100);
47
+ yield rabbit.sendToQueue(queue, payload);
48
+ yield delay(10);
49
+ // expect(mockFn).toBeCalledTimes(i);
50
+ const used = process.memoryUsage().heapUsed / 1024 / 1024;
51
+ console.error('Used', i, used);
52
+ }
53
+ });
54
+ main();
package/dump.rdb ADDED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/rabbit",
3
- "version": "2.3.4",
3
+ "version": "2.3.6",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -0,0 +1,427 @@
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;
package/src/index.ts CHANGED
@@ -65,7 +65,7 @@ const defaultOptions = {
65
65
  useConsumeWithLock: DEFAULT_USE_CONSUME_WITH_LOCK,
66
66
  };
67
67
 
68
- const assertExchangeFanout = (c: any, exchangeName: string) => c.assertExchange(exchangeName, 'fanout');
68
+ const assertExchangeFanout = async (c: any, exchangeName: string) => c.assertExchange(exchangeName, 'fanout');
69
69
 
70
70
  const connectionCreatedConst = 'connectionCreated';
71
71
 
@@ -267,7 +267,7 @@ class RabbitMq implements IAfRabbitMq {
267
267
  try {
268
268
  const connection: AmqpConnectionManager = await this.getConnection();
269
269
  if (this.channel === null) {
270
- this.channel = connection.createChannel({});
270
+ this.channel = await connection.createChannel({});
271
271
  }
272
272
  resolve(this.channel);
273
273
  } catch (e) {
@@ -295,6 +295,7 @@ class RabbitMq implements IAfRabbitMq {
295
295
 
296
296
  async bindQueue(queue: string, exchange: string) {
297
297
  const channel: ChannelWrapper = await this.assertChannel();
298
+ await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.bindQueue(queue, exchange, ''));
298
299
  // @ts-ignore
299
300
  return channel.bindQueue(queue, exchange, '');
300
301
  }
@@ -305,6 +306,7 @@ class RabbitMq implements IAfRabbitMq {
305
306
  if (this.queues[queueName]) {
306
307
  return this.queues[queueName];
307
308
  }
309
+ await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, options));
308
310
  const queue = await channel.assertQueue(queueName, options);
309
311
  this.queues[queueName] = queueName;
310
312
  return queue;
@@ -0,0 +1,44 @@
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();
package/tsconfig.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "target": "es6",
3
+ "target": "es2020",
4
4
  "module": "commonjs",
5
5
  "declaration": true,
6
6
  "outDir": "./dist",