@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 +3 -0
- package/dist/index.d.ts +11 -16
- package/dist/index.js +102 -186
- package/dist/lib/consts.d.ts +0 -5
- package/dist/lib/consts.js +1 -6
- package/dist/lib/redis.d.ts +1 -1
- package/dist/lib/types.d.ts +5 -21
- package/dist/lib/types.js +0 -9
- package/dist/lib/utils.d.ts +0 -1
- package/dist/lib/utils.js +5 -4
- package/package.json +1 -1
- package/src/index.ts +17 -5
package/.env
ADDED
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
|
|
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
|
-
*
|
|
27
|
+
* retry on creation error
|
|
32
28
|
* @default false
|
|
33
29
|
*/
|
|
34
|
-
|
|
35
|
-
rabbitHost?: string;
|
|
30
|
+
retryAssert?: boolean;
|
|
36
31
|
}
|
|
37
|
-
|
|
32
|
+
type newChannelOpts = {
|
|
38
33
|
name?: string;
|
|
39
34
|
onClose?: null | ((args: any | null) => void);
|
|
40
|
-
options?: CreateChannelOpts | undefined;
|
|
41
35
|
};
|
|
42
|
-
|
|
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:
|
|
77
|
-
nack: (channel:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
162
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
249
|
-
|
|
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
|
|
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
|
|
236
|
+
return channel.checkQueue(queue);
|
|
276
237
|
}
|
|
277
238
|
async deleteQueue(queue) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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 (
|
|
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
|
-
|
|
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,
|
|
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
|
|
356
|
-
return channel.addSetup(async (
|
|
357
|
-
await
|
|
358
|
-
await
|
|
359
|
-
const { consumerTag } = await
|
|
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(
|
|
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,
|
|
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
|
|
352
|
+
await this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
|
|
417
353
|
}
|
|
418
|
-
}
|
|
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([
|
|
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(
|
|
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
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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
|
-
|
|
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;
|
package/dist/lib/consts.d.ts
CHANGED
|
@@ -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
|
};
|
package/dist/lib/consts.js
CHANGED
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_OPTIONS = exports.
|
|
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
|
};
|
package/dist/lib/redis.d.ts
CHANGED
package/dist/lib/types.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { ConsumeMessage
|
|
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
|
|
8
|
+
export type CustomMessageHeaders = {
|
|
9
9
|
redisTimestampValidationKey?: string;
|
|
10
10
|
};
|
|
11
|
-
export
|
|
12
|
-
export
|
|
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
|
|
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
|
-
};
|
package/dist/lib/utils.d.ts
CHANGED
|
@@ -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.
|
|
4
|
-
|
|
5
|
-
exports.
|
|
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.
|
|
17
|
+
exports.wrapSetImmediate = wrapSetImmediate;
|
package/package.json
CHANGED
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
|
-
|
|
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
|
|
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
|
-
|
|
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;
|