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