@autofleet/rabbit 4.1.1 → 5.0.1
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/README.md +8 -0
- package/dist/chunk-CUT6urMc.cjs +30 -0
- package/dist/index-B6pk4Ot8.d.cts +216 -0
- package/dist/index-K1ijaudV.d.ts +216 -0
- package/dist/index.cjs +1120 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -132
- package/dist/index.js +1112 -1112
- package/dist/index.js.map +1 -1
- package/dist/mock/index.cjs +20 -0
- package/dist/mock/index.cjs.map +1 -0
- package/dist/mock/index.d.cts +18 -0
- package/dist/mock/index.d.ts +16 -12
- package/dist/mock/index.js +18 -18
- package/dist/mock/index.js.map +1 -1
- package/dist/mock/vitest.cjs +21 -0
- package/dist/mock/vitest.cjs.map +1 -0
- package/dist/mock/vitest.d.cts +18 -0
- package/dist/mock/vitest.d.ts +16 -11
- package/dist/mock/vitest.js +18 -17
- package/dist/mock/vitest.js.map +1 -1
- package/package.json +48 -9
- package/.nvmrc +0 -1
- package/dist/lib/celery.d.ts +0 -9
- package/dist/lib/celery.js +0 -54
- package/dist/lib/celery.js.map +0 -1
- package/dist/lib/consts.d.ts +0 -27
- package/dist/lib/consts.js +0 -31
- package/dist/lib/consts.js.map +0 -1
- package/dist/lib/rabbitError.d.ts +0 -3
- package/dist/lib/rabbitError.js +0 -10
- package/dist/lib/rabbitError.js.map +0 -1
- package/dist/lib/redis.d.ts +0 -8
- package/dist/lib/redis.js +0 -6
- package/dist/lib/redis.js.map +0 -1
- package/dist/lib/types.d.ts +0 -57
- package/dist/lib/types.js +0 -12
- package/dist/lib/types.js.map +0 -1
- package/dist/lib/utils.d.ts +0 -14
- package/dist/lib/utils.js +0 -29
- package/dist/lib/utils.js.map +0 -1
- package/dist/logger.d.ts +0 -2
- package/dist/logger.js +0 -9
- package/dist/logger.js.map +0 -1
- package/src/index.ts +0 -1401
- package/src/lib/celery.ts +0 -89
- package/src/lib/consts.ts +0 -32
- package/src/lib/rabbitError.ts +0 -6
- package/src/lib/redis.ts +0 -11
- package/src/lib/types.ts +0 -77
- package/src/lib/utils.ts +0 -42
- package/src/logger.ts +0 -5
- package/src/mock/index.ts +0 -25
- package/src/mock/vitest.ts +0 -24
- package/src/redis-lock.d.ts +0 -6
- package/tsconfig.build.json +0 -5
- package/tsconfig.json +0 -16
- package/vitest.config.ts +0 -17
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1120 @@
|
|
|
1
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
+
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
3
|
+
const node_process = require_chunk.__toESM(require("node:process"));
|
|
4
|
+
const node_timers_promises = require_chunk.__toESM(require("node:timers/promises"));
|
|
5
|
+
const node_events = require_chunk.__toESM(require("node:events"));
|
|
6
|
+
const moment = require_chunk.__toESM(require("moment"));
|
|
7
|
+
const redis_lock = require_chunk.__toESM(require("redis-lock"));
|
|
8
|
+
const amqp_connection_manager = require_chunk.__toESM(require("amqp-connection-manager"));
|
|
9
|
+
const exponential_backoff = require_chunk.__toESM(require("exponential-backoff"));
|
|
10
|
+
const __autofleet_zehut = require_chunk.__toESM(require("@autofleet/zehut"));
|
|
11
|
+
const node_crypto = require_chunk.__toESM(require("node:crypto"));
|
|
12
|
+
const __autofleet_logger = require_chunk.__toESM(require("@autofleet/logger"));
|
|
13
|
+
const redis = require_chunk.__toESM(require("redis"));
|
|
14
|
+
|
|
15
|
+
//#region src/logger.ts
|
|
16
|
+
const logger = (0, __autofleet_logger.default)();
|
|
17
|
+
var logger_default = logger;
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/lib/rabbitError.ts
|
|
21
|
+
var RabbitError = class extends Error {
|
|
22
|
+
constructor(message) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = "RabbitError";
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/lib/redis.ts
|
|
30
|
+
const getRedisInstance = (config$1) => (0, redis.createClient)({ socket: config$1 });
|
|
31
|
+
var redis_default = getRedisInstance;
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/lib/consts.ts
|
|
35
|
+
const DEFAULT_DEAD_TTL_TWO_DAYS = 6e4 * 60 * 12;
|
|
36
|
+
const DEFAULT_LOCK_TIMEOUT = 1e3 * 5;
|
|
37
|
+
const RETRY_HEADER = "x-retry-count";
|
|
38
|
+
const TRACING_HEADER = "x-trace-id";
|
|
39
|
+
const USER_TRACING_HEADER = "x-af-user-id";
|
|
40
|
+
const AUTOMATION_ID_HEADER = "x-af-automation-id";
|
|
41
|
+
const DEFAULT_USE_CONSUME_WITH_LOCK = false;
|
|
42
|
+
const DEFAULT_OPTIONS = {
|
|
43
|
+
limit: 1,
|
|
44
|
+
retries: 1,
|
|
45
|
+
deadMessageTtl: DEFAULT_DEAD_TTL_TWO_DAYS,
|
|
46
|
+
lockTimeout: DEFAULT_LOCK_TIMEOUT,
|
|
47
|
+
useConsumeWithLock: DEFAULT_USE_CONSUME_WITH_LOCK,
|
|
48
|
+
auditContext: null,
|
|
49
|
+
enableRabbitTrace: false
|
|
50
|
+
};
|
|
51
|
+
const ASSERT_VHOST_EXPONENTIAL_BACKOFF_DEV_ENV_CONFIG = {
|
|
52
|
+
startingDelay: 500,
|
|
53
|
+
timeMultiple: 4,
|
|
54
|
+
numOfAttempts: 5
|
|
55
|
+
};
|
|
56
|
+
const ASSERT_VHOST_EXPONENTIAL_BACKOFF_PROD_CONFIG = {
|
|
57
|
+
startingDelay: 1,
|
|
58
|
+
timeMultiple: 1,
|
|
59
|
+
numOfAttempts: 5
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/lib/utils.ts
|
|
64
|
+
const { PROJECT_ID } = process.env;
|
|
65
|
+
const assertExchangeFanout = async (c, exchangeName) => c.assertExchange(exchangeName, "fanout");
|
|
66
|
+
const rand = () => Math.floor(Math.random() * 1e5);
|
|
67
|
+
/** This is polyfill for `Promise.withResolvers` which exists only on Node v22 and onwards */
|
|
68
|
+
const createDeferredPromise = () => {
|
|
69
|
+
if (Promise.withResolvers) return Promise.withResolvers();
|
|
70
|
+
let resolve;
|
|
71
|
+
let reject;
|
|
72
|
+
const promise = new Promise((res, rej) => {
|
|
73
|
+
resolve = res;
|
|
74
|
+
reject = rej;
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
promise,
|
|
78
|
+
resolve,
|
|
79
|
+
reject
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
const isDevEnv = () => ["af-experiment-manager", "dev1-experiment-manager"].includes(PROJECT_ID || "");
|
|
83
|
+
const getAssertVhostExponentialBackoffConfig = () => isDevEnv() ? ASSERT_VHOST_EXPONENTIAL_BACKOFF_DEV_ENV_CONFIG : ASSERT_VHOST_EXPONENTIAL_BACKOFF_PROD_CONFIG;
|
|
84
|
+
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/lib/types.ts
|
|
87
|
+
const HA_PROMOTE_ON_FAILURE = "ha-promote-on-failure";
|
|
88
|
+
const HA_PROMOTE_ON_SHUTDOWN = "ha-promote-on-shutdown";
|
|
89
|
+
const CONSUMER_DEFAULT_OPTIONS = { arguments: {
|
|
90
|
+
[HA_PROMOTE_ON_FAILURE]: "always",
|
|
91
|
+
[HA_PROMOTE_ON_SHUTDOWN]: "always"
|
|
92
|
+
} };
|
|
93
|
+
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region src/lib/celery.ts
|
|
96
|
+
const config = {
|
|
97
|
+
host: process.env.RABBITMQ_SERVICE_HOST || "localhost",
|
|
98
|
+
username: process.env.RABBITMQ_USERNAME || "guest",
|
|
99
|
+
password: process.env.RABBITMQ_PASSWORD || "guest"
|
|
100
|
+
};
|
|
101
|
+
async function sendCeleryTaskViaHttp(data, { taskName, queueName }) {
|
|
102
|
+
const apiUrl = `http://${config.host}:15672/api/exchanges/%2f/amq.default/publish`;
|
|
103
|
+
const message = {
|
|
104
|
+
task: taskName,
|
|
105
|
+
id: (0, node_crypto.randomUUID)(),
|
|
106
|
+
args: [data]
|
|
107
|
+
};
|
|
108
|
+
const payload = {
|
|
109
|
+
properties: {
|
|
110
|
+
delivery_mode: 2,
|
|
111
|
+
content_type: "application/json"
|
|
112
|
+
},
|
|
113
|
+
routing_key: queueName,
|
|
114
|
+
payload: JSON.stringify(message),
|
|
115
|
+
payload_encoding: "string"
|
|
116
|
+
};
|
|
117
|
+
try {
|
|
118
|
+
const response = await fetch(apiUrl, {
|
|
119
|
+
method: "POST",
|
|
120
|
+
headers: {
|
|
121
|
+
"Content-Type": "application/json",
|
|
122
|
+
Authorization: `Basic ${Buffer.from(`${config.username}:${config.password}`).toString("base64")}`
|
|
123
|
+
},
|
|
124
|
+
body: JSON.stringify(payload)
|
|
125
|
+
});
|
|
126
|
+
if (response.ok) {
|
|
127
|
+
const result = await response.json();
|
|
128
|
+
logger_default.info("Successfully published message:", result);
|
|
129
|
+
} else {
|
|
130
|
+
logger_default.error(`Failed to publish message. Status code: ${response.status}`);
|
|
131
|
+
logger_default.error(`Response: ${await response.text()}`);
|
|
132
|
+
}
|
|
133
|
+
} catch (error) {
|
|
134
|
+
logger_default.error("Error sending request:", error instanceof Error ? error.message : String(error));
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region src/index.ts
|
|
141
|
+
const PUBLISH_TIMEOUT = 1e3 * 10;
|
|
142
|
+
const { DISABLE_QUORUM_QUEUES_CONSUME, DISABLE_QUORUM_QUEUES_PUBLISH } = node_process.env;
|
|
143
|
+
const HEARTBEAT = "60";
|
|
144
|
+
var RabbitMq = class RabbitMq {
|
|
145
|
+
static parseMsg(msg) {
|
|
146
|
+
let content = msg.content.toString();
|
|
147
|
+
try {
|
|
148
|
+
content = JSON.parse(content);
|
|
149
|
+
} catch {}
|
|
150
|
+
return {
|
|
151
|
+
...msg,
|
|
152
|
+
content
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
static validateName(type, name) {
|
|
156
|
+
if (!name || name === "") throw new RabbitError(`error while using ${type} with no name`);
|
|
157
|
+
}
|
|
158
|
+
static getPublishOptions(customHeaders = {}) {
|
|
159
|
+
const user = (0, __autofleet_zehut.getUser)();
|
|
160
|
+
const traceId = __autofleet_zehut.outbreak.getCurrentContextTraceId();
|
|
161
|
+
return {
|
|
162
|
+
timestamp: (0, moment.default)().unix(),
|
|
163
|
+
timeout: PUBLISH_TIMEOUT,
|
|
164
|
+
headers: {
|
|
165
|
+
creationTimestamp: (0, moment.default)().valueOf(),
|
|
166
|
+
...customHeaders,
|
|
167
|
+
[USER_TRACING_HEADER]: user?.id,
|
|
168
|
+
[__autofleet_zehut.CONTEXTS_IDS_HEADER]: user?.contextIds,
|
|
169
|
+
[TRACING_HEADER]: traceId
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
DISCONNECT_MSG = "rabbit: connection disconnect";
|
|
174
|
+
RECONNECT_MSG = "rabbit: connection disconnect - reconnecting";
|
|
175
|
+
publishChannel = null;
|
|
176
|
+
publishChannelSetupPromise = null;
|
|
177
|
+
publishConnection = {
|
|
178
|
+
amqpConnection: null,
|
|
179
|
+
creatingConnection: false,
|
|
180
|
+
connectionCreatedEventName: "publishConnectionCreated",
|
|
181
|
+
connectionFailedEventName: "publishConnectionFailed",
|
|
182
|
+
blockReconnect: false
|
|
183
|
+
};
|
|
184
|
+
consumeConnection = {
|
|
185
|
+
amqpConnection: null,
|
|
186
|
+
creatingConnection: false,
|
|
187
|
+
connectionCreatedEventName: "consumeConnectionCreated",
|
|
188
|
+
connectionFailedEventName: "consumeConnectionFailed",
|
|
189
|
+
blockReconnect: false
|
|
190
|
+
};
|
|
191
|
+
em = new node_events.EventEmitter();
|
|
192
|
+
exchanges = {};
|
|
193
|
+
queues = {};
|
|
194
|
+
queueSetupPromises = {};
|
|
195
|
+
assertExchangePromises = {};
|
|
196
|
+
redisClient;
|
|
197
|
+
redisLock;
|
|
198
|
+
/** A map of consumers' tags used for canceling consumption */
|
|
199
|
+
consumersTags = /* @__PURE__ */ new Map();
|
|
200
|
+
consumersToRegister = [];
|
|
201
|
+
doesVHostExist = false;
|
|
202
|
+
vhost = "quorum-vhost";
|
|
203
|
+
gracefulShutdownStarted = false;
|
|
204
|
+
oldPublishChannel = null;
|
|
205
|
+
oldPublishChannelSetupPromise = null;
|
|
206
|
+
oldPublishConnection = {
|
|
207
|
+
amqpConnection: null,
|
|
208
|
+
creatingConnection: false,
|
|
209
|
+
connectionCreatedEventName: "oldPublishConnectionCreated",
|
|
210
|
+
connectionFailedEventName: "oldPublishConnectionFailed",
|
|
211
|
+
blockReconnect: false
|
|
212
|
+
};
|
|
213
|
+
oldConsumeConnection = {
|
|
214
|
+
amqpConnection: null,
|
|
215
|
+
creatingConnection: false,
|
|
216
|
+
connectionCreatedEventName: "oldConsumeConnectionCreated",
|
|
217
|
+
connectionFailedEventName: "oldConsumeConnectionFailed",
|
|
218
|
+
blockReconnect: false
|
|
219
|
+
};
|
|
220
|
+
oldEm = new node_events.EventEmitter();
|
|
221
|
+
oldExchanges = {};
|
|
222
|
+
oldQueues = {};
|
|
223
|
+
oldQueueSetupPromises = {};
|
|
224
|
+
oldAssertExchangePromises = {};
|
|
225
|
+
oldConsumersTags = /* @__PURE__ */ new Map();
|
|
226
|
+
oldConsumersToRegister = [];
|
|
227
|
+
#logger;
|
|
228
|
+
constructor(options = {}, redisConfig) {
|
|
229
|
+
this.options = options;
|
|
230
|
+
this.redisConfig = redisConfig;
|
|
231
|
+
this.#logger = options?.logger || logger_default;
|
|
232
|
+
if (redisConfig) {
|
|
233
|
+
this.redisClient = redis_default(redisConfig).on("error", (err) => {
|
|
234
|
+
this.#logger.error("rabbit: Redis error", {
|
|
235
|
+
err,
|
|
236
|
+
redisConfig
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
this.redisClient.connect().catch((err) => {
|
|
240
|
+
this.#logger.error("rabbit: Failed to connect to Redis", {
|
|
241
|
+
err,
|
|
242
|
+
redisConfig
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
this.redisLock = (0, redis_lock.default)(this.redisClient);
|
|
246
|
+
}
|
|
247
|
+
this.#logger.info(`rabbit: [gracefully-shutdown] adding gracefully shutdown for process.pid ${process.pid}`);
|
|
248
|
+
if (!this.options?.dontGracefulShutdown) {
|
|
249
|
+
process.on("SIGTERM", async () => {
|
|
250
|
+
await this.gracefulShutdown("SIGTERM");
|
|
251
|
+
});
|
|
252
|
+
process.on("SIGINT", async () => {
|
|
253
|
+
await this.gracefulShutdown("SIGINT");
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
getRedisKey(key) {
|
|
258
|
+
return `${this.redisConfig?.prefix || ""}${key}`;
|
|
259
|
+
}
|
|
260
|
+
assertVHost = async () => {
|
|
261
|
+
if (this.doesVHostExist) return;
|
|
262
|
+
const username = process.env.RABBITMQ_USERNAME || "guest";
|
|
263
|
+
const password = process.env.RABBITMQ_PASSWORD || "guest";
|
|
264
|
+
const credentials = Buffer.from(`${username}:${password}`).toString("base64");
|
|
265
|
+
const headers = {
|
|
266
|
+
Authorization: `Basic ${credentials}`,
|
|
267
|
+
"Content-Type": "application/json"
|
|
268
|
+
};
|
|
269
|
+
const rabbitHost = `http://${(this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || "localhost").split(":")[0]}:15672`;
|
|
270
|
+
const url = `${rabbitHost}/api/vhosts/${encodeURIComponent(this.vhost)}`;
|
|
271
|
+
try {
|
|
272
|
+
const response = await fetch(url, {
|
|
273
|
+
method: "GET",
|
|
274
|
+
headers
|
|
275
|
+
});
|
|
276
|
+
if (response.status === 200) {
|
|
277
|
+
this.doesVHostExist = true;
|
|
278
|
+
this.#logger.info("Vhost exists", { vhost: this.vhost });
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (response.status !== 404) {
|
|
282
|
+
this.#logger.error("Failed to check vhost", { response });
|
|
283
|
+
throw new RabbitError("Failed to check vhost");
|
|
284
|
+
}
|
|
285
|
+
const createResponse = await fetch(url, {
|
|
286
|
+
method: "PUT",
|
|
287
|
+
headers,
|
|
288
|
+
body: JSON.stringify({ default_queue_type: "quorum" })
|
|
289
|
+
});
|
|
290
|
+
if (!createResponse.ok) {
|
|
291
|
+
this.#logger.error("Failed to create vhost", { response: createResponse });
|
|
292
|
+
throw new RabbitError("Failed to create vhost");
|
|
293
|
+
}
|
|
294
|
+
this.doesVHostExist = true;
|
|
295
|
+
this.#logger.info("Vhost created", { vhost: this.vhost });
|
|
296
|
+
} catch (error) {
|
|
297
|
+
this.#logger.error("Failed to check or create vhost", { error });
|
|
298
|
+
throw error;
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
shouldConsumeMessageByTimestamp = async (msg) => {
|
|
302
|
+
if (!msg) return false;
|
|
303
|
+
const { headers } = msg.properties;
|
|
304
|
+
const timestamp = headers?.creationTimestamp;
|
|
305
|
+
if (timestamp && headers?.redisTimestampValidationKey && this.redisClient) {
|
|
306
|
+
const key = this.getRedisKey(headers.redisTimestampValidationKey);
|
|
307
|
+
const lastMessageTimestamp = await this.redisClient.get(key);
|
|
308
|
+
return !lastMessageTimestamp || parseInt(lastMessageTimestamp, 10) <= parseInt(timestamp, 10);
|
|
309
|
+
}
|
|
310
|
+
return true;
|
|
311
|
+
};
|
|
312
|
+
ack = (channel, msg, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg) => {
|
|
313
|
+
if (!msg) return;
|
|
314
|
+
this.#logger.debug("rabbit acking message", { deliveryTag: msg.fields.deliveryTag });
|
|
315
|
+
await channel.ack(msg);
|
|
316
|
+
const { headers } = msg.properties;
|
|
317
|
+
const timestamp = headers?.creationTimestamp;
|
|
318
|
+
if (shouldUpdateRedisTimestamp && timestamp && headers?.redisTimestampValidationKey && this.redisClient) {
|
|
319
|
+
const parsedTimestamp = parseInt(timestamp, 10);
|
|
320
|
+
const key = this.getRedisKey(headers.redisTimestampValidationKey);
|
|
321
|
+
await this.redisClient.set(key, parsedTimestamp, { EX: 3600 });
|
|
322
|
+
await this.unlockRedisIfNeeded(releaseLock);
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
nack = (channel, queue, options, deadQueueOptions, msg, releaseLock) => async (userMsg, { skipRetry = false } = {}) => {
|
|
326
|
+
await this.unlockRedisIfNeeded(releaseLock);
|
|
327
|
+
if (!channel || !msg) {
|
|
328
|
+
this.#logger.error("no channel or msg", { msg });
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const currentRetryHeader = Number.parseInt(msg.properties.headers?.[RETRY_HEADER] || "0", 10) || 0;
|
|
332
|
+
const sendToDeadQueue = skipRetry || currentRetryHeader >= options.retries;
|
|
333
|
+
await this.sendToQueue(`${queue}${sendToDeadQueue ? "-dead" : ""}`, RabbitMq.parseMsg(msg).content, sendToDeadQueue ? deadQueueOptions : options, {
|
|
334
|
+
...msg.properties.headers,
|
|
335
|
+
[RETRY_HEADER]: currentRetryHeader + 1
|
|
336
|
+
});
|
|
337
|
+
this.#logger.debug("rabbit nacking message", { deliveryTag: msg.fields.deliveryTag });
|
|
338
|
+
await channel.ack(msg);
|
|
339
|
+
};
|
|
340
|
+
async getConnection(connection) {
|
|
341
|
+
const { amqpConnection: connectionLocal, creatingConnection, connectionCreatedEventName, connectionFailedEventName, blockReconnect } = connection;
|
|
342
|
+
if (blockReconnect) {
|
|
343
|
+
this.#logger.debug("rabbit: block reconnect");
|
|
344
|
+
return void 0;
|
|
345
|
+
}
|
|
346
|
+
if (connectionLocal !== null) {
|
|
347
|
+
if (this.options?.disableReconnect || connectionLocal?.isConnected()) {
|
|
348
|
+
this.#logger.debug("rabbit: connection - is connected");
|
|
349
|
+
return connectionLocal;
|
|
350
|
+
}
|
|
351
|
+
this.#logger.debug("rabbit: connection - reconnecting");
|
|
352
|
+
}
|
|
353
|
+
if (creatingConnection) {
|
|
354
|
+
this.#logger.debug("rabbit: creating connection emi");
|
|
355
|
+
const [event, value] = await Promise.race([connectionCreatedEventName, connectionFailedEventName].map((e) => (0, node_events.once)(this.em, e).then(([result]) => [e, result])));
|
|
356
|
+
if (event === connectionCreatedEventName) return value;
|
|
357
|
+
throw value;
|
|
358
|
+
}
|
|
359
|
+
connection.creatingConnection = true;
|
|
360
|
+
let isResolved = false;
|
|
361
|
+
const findServers = () => {
|
|
362
|
+
const userName = process.env.RABBITMQ_USERNAME || "guest";
|
|
363
|
+
const password = process.env.RABBITMQ_PASSWORD || "guest";
|
|
364
|
+
const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || "localhost";
|
|
365
|
+
this.#logger.debug("rabbit: creating connection", {
|
|
366
|
+
host,
|
|
367
|
+
userName,
|
|
368
|
+
HEARTBEAT
|
|
369
|
+
});
|
|
370
|
+
return [`amqp://${userName}:${password}@${host}/${this.vhost}?heartbeat=${HEARTBEAT}`];
|
|
371
|
+
};
|
|
372
|
+
const defaultUrls = findServers();
|
|
373
|
+
const newConnection = (0, amqp_connection_manager.connect)(defaultUrls, { findServers });
|
|
374
|
+
connection.amqpConnection = newConnection;
|
|
375
|
+
const { promise, reject, resolve } = createDeferredPromise();
|
|
376
|
+
newConnection.on("error", (err) => {
|
|
377
|
+
this.#logger.error("rabbit: connection error", { err });
|
|
378
|
+
if (!isResolved) {
|
|
379
|
+
isResolved = true;
|
|
380
|
+
reject(err);
|
|
381
|
+
this.em.emit(connectionFailedEventName, err);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
newConnection.on("connectFailed", (err) => {
|
|
385
|
+
this.consumersTags.clear();
|
|
386
|
+
if (typeof err.url === "string") err.url = this.maskURL(err.url);
|
|
387
|
+
this.#logger.error("rabbit: connection connectFailed", {
|
|
388
|
+
err,
|
|
389
|
+
advice: "Check if the vhost exist",
|
|
390
|
+
vhost: this.vhost
|
|
391
|
+
});
|
|
392
|
+
if (!isResolved) {
|
|
393
|
+
isResolved = true;
|
|
394
|
+
reject(err);
|
|
395
|
+
this.em.emit(connectionFailedEventName, err);
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
newConnection.on("disconnect", ({ err }) => {
|
|
399
|
+
this.consumersTags.clear();
|
|
400
|
+
this.#logger.debug("rabbit: connection closed");
|
|
401
|
+
if (this.options?.disableReconnect) {
|
|
402
|
+
this.#logger.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
|
|
403
|
+
connection.blockReconnect = true;
|
|
404
|
+
} else this.#logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
405
|
+
});
|
|
406
|
+
newConnection.once("connect", async () => {
|
|
407
|
+
this.#logger.debug("rabbit: connection established");
|
|
408
|
+
connection.creatingConnection = false;
|
|
409
|
+
this.em.emit(connectionCreatedEventName, newConnection);
|
|
410
|
+
isResolved = true;
|
|
411
|
+
resolve(newConnection);
|
|
412
|
+
});
|
|
413
|
+
return promise;
|
|
414
|
+
}
|
|
415
|
+
async getNewChannel({ name = rand().toString(), onClose = null, options = {}, connection }) {
|
|
416
|
+
let localConnection;
|
|
417
|
+
try {
|
|
418
|
+
localConnection = await this.getConnection(connection);
|
|
419
|
+
} catch (e) {
|
|
420
|
+
this.#logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
|
|
421
|
+
throw e;
|
|
422
|
+
}
|
|
423
|
+
const channel = localConnection?.createChannel({ ...options });
|
|
424
|
+
(0, node_events.once)(channel, "close").then((args) => {
|
|
425
|
+
this.#logger.error(`rabbit: channel ${name} closed`);
|
|
426
|
+
onClose?.(args);
|
|
427
|
+
});
|
|
428
|
+
try {
|
|
429
|
+
await (0, node_events.once)(channel, "connect");
|
|
430
|
+
this.#logger.debug(`rabbit: channel ${name} CONNECTED`);
|
|
431
|
+
return channel;
|
|
432
|
+
} catch (err) {
|
|
433
|
+
this.#logger.error(`rabbit: channel error ${name} error`, { err });
|
|
434
|
+
throw err;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
async assertChannel({ force = false, connection }) {
|
|
438
|
+
if (this.publishChannelSetupPromise) return this.publishChannelSetupPromise;
|
|
439
|
+
const { promise, resolve, reject } = createDeferredPromise();
|
|
440
|
+
this.publishChannelSetupPromise = promise;
|
|
441
|
+
if (this.publishChannel && !force) {
|
|
442
|
+
resolve(this.publishChannel);
|
|
443
|
+
return promise;
|
|
444
|
+
}
|
|
445
|
+
try {
|
|
446
|
+
const channel = await this.getNewChannel({ connection });
|
|
447
|
+
channel.on("error", (err) => {
|
|
448
|
+
this.#logger.error("rabbit: channel error", { err });
|
|
449
|
+
});
|
|
450
|
+
if (this.publishConnection === connection) this.publishChannel = channel;
|
|
451
|
+
resolve(channel);
|
|
452
|
+
} catch (e) {
|
|
453
|
+
reject(e);
|
|
454
|
+
}
|
|
455
|
+
return promise;
|
|
456
|
+
}
|
|
457
|
+
async assertExchange(exchangeName, connection) {
|
|
458
|
+
const channel = await this.assertChannel({ connection });
|
|
459
|
+
if (this.exchanges[exchangeName]) {
|
|
460
|
+
delete this.assertExchangePromises[exchangeName];
|
|
461
|
+
return this.exchanges[exchangeName];
|
|
462
|
+
}
|
|
463
|
+
if (this.assertExchangePromises[exchangeName]) return this.assertExchangePromises[exchangeName];
|
|
464
|
+
this.assertExchangePromises[exchangeName] = assertExchangeFanout(channel, exchangeName);
|
|
465
|
+
this.exchanges[exchangeName] = await this.assertExchangePromises[exchangeName];
|
|
466
|
+
return this.exchanges[exchangeName];
|
|
467
|
+
}
|
|
468
|
+
async getQueueLength(queue) {
|
|
469
|
+
RabbitMq.validateName("queue", queue);
|
|
470
|
+
const { publishChannel: channel } = this;
|
|
471
|
+
if (!channel) throw new RabbitError("channel is not defined");
|
|
472
|
+
this.#logger.debug("rabbit: getting queue length", {
|
|
473
|
+
queue,
|
|
474
|
+
connected: this.publishConnection.amqpConnection?.isConnected()
|
|
475
|
+
});
|
|
476
|
+
return channel?.checkQueue(queue);
|
|
477
|
+
}
|
|
478
|
+
async deleteQueue(queue, connection) {
|
|
479
|
+
RabbitMq.validateName("queue", queue);
|
|
480
|
+
const channel = await this.assertChannel({ connection });
|
|
481
|
+
this.#logger.info("rabbit: deleting queue", { queue });
|
|
482
|
+
const deleteQueueRes = await channel.deleteQueue(queue);
|
|
483
|
+
this.#logger.debug("queue deleted", deleteQueueRes);
|
|
484
|
+
return deleteQueueRes;
|
|
485
|
+
}
|
|
486
|
+
async bindQueue(queue, exchange) {
|
|
487
|
+
const channel = await this.assertChannel({ connection: this.publishConnection });
|
|
488
|
+
await channel.addSetup((setupChannel) => setupChannel.bindQueue(queue, exchange, ""));
|
|
489
|
+
return channel.bindQueue(queue, exchange, "");
|
|
490
|
+
}
|
|
491
|
+
async setupQueue(queueName, options) {
|
|
492
|
+
let queue;
|
|
493
|
+
const connection = this.publishConnection;
|
|
494
|
+
const localeOptions = {
|
|
495
|
+
...options,
|
|
496
|
+
durable: true,
|
|
497
|
+
arguments: {
|
|
498
|
+
...options?.arguments,
|
|
499
|
+
"x-consumer-timeout": 1e3 * 60 * 60 * 24,
|
|
500
|
+
"x-queue-type": "quorum"
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
try {
|
|
504
|
+
const channel = await this.assertChannel({ connection });
|
|
505
|
+
this.#logger.debug("assertQueue->channel.addSetup", { queueName });
|
|
506
|
+
await channel.addSetup(async (setupChannel) => {
|
|
507
|
+
await setupChannel.assertQueue(queueName, localeOptions);
|
|
508
|
+
});
|
|
509
|
+
this.#logger.debug("assertQueue->channel.assertQueue", { queueName });
|
|
510
|
+
queue = await channel.assertQueue(queueName, localeOptions);
|
|
511
|
+
} catch (e) {
|
|
512
|
+
this.#logger.error("rabbit: assertQueue error", {
|
|
513
|
+
queueName,
|
|
514
|
+
options,
|
|
515
|
+
error: e
|
|
516
|
+
});
|
|
517
|
+
if (!this.options?.dontRetryAssert) {
|
|
518
|
+
this.#logger.debug("retrying assertQueue", { queueName });
|
|
519
|
+
const channel = await this.assertChannel({
|
|
520
|
+
force: true,
|
|
521
|
+
connection
|
|
522
|
+
});
|
|
523
|
+
await this.deleteQueue(queueName, connection);
|
|
524
|
+
this.#logger.debug("retrying assertQueue->channel.addSetup", { queueName });
|
|
525
|
+
await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
526
|
+
this.#logger.debug("retrying assertQueue->channel.assertQueue", { queueName });
|
|
527
|
+
queue = await channel.assertQueue(queueName, localeOptions);
|
|
528
|
+
} else throw e;
|
|
529
|
+
}
|
|
530
|
+
this.queues[queueName] = queue;
|
|
531
|
+
return queue;
|
|
532
|
+
}
|
|
533
|
+
static shouldUseQuorum(queueName) {
|
|
534
|
+
const envQuorumQueuesWhitelist = process.env.QUORUM_QUEUES_WHITELIST;
|
|
535
|
+
if (envQuorumQueuesWhitelist === "*") return true;
|
|
536
|
+
if (envQuorumQueuesWhitelist) {
|
|
537
|
+
const whitelist = envQuorumQueuesWhitelist.split(",");
|
|
538
|
+
return whitelist.includes(queueName);
|
|
539
|
+
}
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
async assertQueue(queueName, options) {
|
|
543
|
+
RabbitMq.validateName("queue", queueName);
|
|
544
|
+
if (this.queues[queueName]) {
|
|
545
|
+
delete this.queueSetupPromises[queueName];
|
|
546
|
+
return this.queues[queueName];
|
|
547
|
+
}
|
|
548
|
+
this.queueSetupPromises[queueName] ||= this.setupQueue(queueName, options);
|
|
549
|
+
return this.queueSetupPromises[queueName];
|
|
550
|
+
}
|
|
551
|
+
saveConsumer(queue, callback, options) {
|
|
552
|
+
const isConsumerExist = this.consumersToRegister.some((consumer) => consumer.queue === queue);
|
|
553
|
+
if (!isConsumerExist) {
|
|
554
|
+
this.#logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
|
|
555
|
+
this.consumersToRegister.push({
|
|
556
|
+
queue,
|
|
557
|
+
callback,
|
|
558
|
+
options
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
async consume(queue, callback, options) {
|
|
563
|
+
this.saveConsumerOld(queue, callback, options);
|
|
564
|
+
if (options?.isQuorumQueue !== false && DISABLE_QUORUM_QUEUES_CONSUME !== "true") {
|
|
565
|
+
this.saveConsumer(queue, callback, options);
|
|
566
|
+
const backoffConfig = getAssertVhostExponentialBackoffConfig();
|
|
567
|
+
await (0, exponential_backoff.backOff)(() => this.assertVHost(), backoffConfig);
|
|
568
|
+
await this.consumeNew(queue, callback, options);
|
|
569
|
+
}
|
|
570
|
+
await this.consumeOld(queue, callback, options);
|
|
571
|
+
}
|
|
572
|
+
async consumeNew(queue, callback, options) {
|
|
573
|
+
await this.consumeFromRabbit(queue, callback, options);
|
|
574
|
+
}
|
|
575
|
+
async consumeOld(queue, callback, options) {
|
|
576
|
+
await this.consumeFromRabbitOld(queue, callback, options);
|
|
577
|
+
}
|
|
578
|
+
async lockRedisIfNeeded(msg, options) {
|
|
579
|
+
const { properties: { headers } } = msg;
|
|
580
|
+
const timestamp = headers?.creationTimestamp;
|
|
581
|
+
let releaseLock = null;
|
|
582
|
+
if (options.useConsumeWithLock && timestamp && headers?.redisTimestampValidationKey && this.redisLock) releaseLock = await this.redisLock(headers.redisTimestampValidationKey, options?.lockTimeout || DEFAULT_LOCK_TIMEOUT);
|
|
583
|
+
return releaseLock;
|
|
584
|
+
}
|
|
585
|
+
async unlockRedisIfNeeded(releaseLock) {
|
|
586
|
+
if (this.redisLock && releaseLock) await releaseLock();
|
|
587
|
+
}
|
|
588
|
+
async consumeFromRabbit(queue, callback, options) {
|
|
589
|
+
const optionsWithDefaults = {
|
|
590
|
+
...DEFAULT_OPTIONS,
|
|
591
|
+
...options
|
|
592
|
+
};
|
|
593
|
+
RabbitMq.validateName("queue", queue);
|
|
594
|
+
const uniqueId = (0, node_crypto.randomUUID)();
|
|
595
|
+
const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace } = optionsWithDefaults;
|
|
596
|
+
if (useConsumeWithLock) {
|
|
597
|
+
if (!this.redisLock) throw new RabbitError("Usage of consumeWithLock requires RedisInstance");
|
|
598
|
+
this.#logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
599
|
+
}
|
|
600
|
+
const channel = await this.getNewChannel({ connection: this.consumeConnection });
|
|
601
|
+
return channel.addSetup(async (confirmChannel) => {
|
|
602
|
+
await this.assertQueue(queue, optionsWithDefaults);
|
|
603
|
+
await confirmChannel.prefetch(limit, false);
|
|
604
|
+
const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
|
|
605
|
+
if (!msg) return null;
|
|
606
|
+
const { [TRACING_HEADER]: traceId, [USER_TRACING_HEADER]: userId, [AUTOMATION_ID_HEADER]: automationId, [__autofleet_zehut.CONTEXTS_IDS_HEADER]: userContextIds } = msg.properties.headers || {};
|
|
607
|
+
const parsedMessage = RabbitMq.parseMsg(msg);
|
|
608
|
+
const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
|
|
609
|
+
const trace = (0, __autofleet_zehut.newTrace)(__autofleet_zehut.traceTypes.RABBIT);
|
|
610
|
+
if (userId && enableRabbitTrace) try {
|
|
611
|
+
await (0, __autofleet_zehut.createOrSetRabbitTrace)(trace, userId, userContextIds);
|
|
612
|
+
} catch (e) {
|
|
613
|
+
this.#logger.error("rabbit: failed to setRabbitTrace", {
|
|
614
|
+
userId,
|
|
615
|
+
e
|
|
616
|
+
});
|
|
617
|
+
return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
|
|
618
|
+
}
|
|
619
|
+
if (traceId) trace.context?.set(TRACING_HEADER, traceId);
|
|
620
|
+
if (auditContext) await auditContext(queue, {
|
|
621
|
+
userId,
|
|
622
|
+
automationId
|
|
623
|
+
});
|
|
624
|
+
const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
|
|
625
|
+
if (!shouldConsume) {
|
|
626
|
+
await this.unlockRedisIfNeeded(releaseLock);
|
|
627
|
+
return this.ack(confirmChannel, msg)(msg);
|
|
628
|
+
}
|
|
629
|
+
let messageAcked = false;
|
|
630
|
+
const localAck = async () => {
|
|
631
|
+
if (messageAcked) return;
|
|
632
|
+
messageAcked = true;
|
|
633
|
+
await this.ack(confirmChannel, msg, true, releaseLock)(msg);
|
|
634
|
+
};
|
|
635
|
+
const localNack = async (_, nackOptions = {}) => {
|
|
636
|
+
if (messageAcked) return;
|
|
637
|
+
this.#logger.debug("rabbit localNack", {
|
|
638
|
+
messageAcked,
|
|
639
|
+
uniqueId,
|
|
640
|
+
deliveryTag: msg.fields.deliveryTag
|
|
641
|
+
});
|
|
642
|
+
messageAcked = true;
|
|
643
|
+
await this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
|
|
644
|
+
};
|
|
645
|
+
try {
|
|
646
|
+
return await callback(parsedMessage, localAck, localNack);
|
|
647
|
+
} catch (e) {
|
|
648
|
+
return localNack(msg);
|
|
649
|
+
}
|
|
650
|
+
}, CONSUMER_DEFAULT_OPTIONS);
|
|
651
|
+
if (!consumerTag) this.#logger.error(`rabbit: failed to consume from queue ${queue}`);
|
|
652
|
+
else {
|
|
653
|
+
this.#logger.info(`rabbit: adding tag ${consumerTag} to the array.`);
|
|
654
|
+
this.consumersTags.set(queue, {
|
|
655
|
+
channel: confirmChannel,
|
|
656
|
+
consumerTag
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
async consumeFromExchange(queue, exchange, callback, options) {
|
|
662
|
+
const optionsWithDefaults = {
|
|
663
|
+
...DEFAULT_OPTIONS,
|
|
664
|
+
...options
|
|
665
|
+
};
|
|
666
|
+
RabbitMq.validateName("exchange", exchange);
|
|
667
|
+
RabbitMq.validateName("queue", queue);
|
|
668
|
+
const { limit } = optionsWithDefaults;
|
|
669
|
+
if (options?.isQuorumQueue !== false && DISABLE_QUORUM_QUEUES_CONSUME !== "true") {
|
|
670
|
+
await this.saveConsumer(queue, callback, options);
|
|
671
|
+
const backoffConfig = getAssertVhostExponentialBackoffConfig();
|
|
672
|
+
await (0, exponential_backoff.backOff)(() => this.assertVHost(), backoffConfig);
|
|
673
|
+
const channel = await this.getNewChannel({
|
|
674
|
+
name: `consume-exchange-${exchange}-queue-${queue}`,
|
|
675
|
+
connection: this.consumeConnection
|
|
676
|
+
});
|
|
677
|
+
await channel.addSetup(async (c) => {
|
|
678
|
+
const assertExchange = await assertExchangeFanout(c, exchange);
|
|
679
|
+
await c.assertQueue(queue);
|
|
680
|
+
this.exchanges[exchange] = assertExchange;
|
|
681
|
+
await c.prefetch(limit, false);
|
|
682
|
+
return Promise.all([c.bindQueue(queue, exchange, ""), this.consumeNew(queue, callback, options)]);
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
await this.saveConsumerOld(queue, callback, options);
|
|
686
|
+
const channelOld = await this.getNewChannelOld({
|
|
687
|
+
name: `consume-exchange-${exchange}-queue-${queue}-old`,
|
|
688
|
+
connection: this.oldConsumeConnection
|
|
689
|
+
});
|
|
690
|
+
await channelOld.addSetup(async (c) => {
|
|
691
|
+
const assertExchange = await assertExchangeFanout(c, exchange);
|
|
692
|
+
await c.assertQueue(queue);
|
|
693
|
+
this.oldExchanges[exchange] = assertExchange;
|
|
694
|
+
await c.prefetch(limit, false);
|
|
695
|
+
return Promise.all([c.bindQueue(queue, exchange, ""), this.consumeOld(queue, callback, options)]);
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
async publish(exchange, content, customHeaders, isQuorumQueue = true) {
|
|
699
|
+
await (0, node_timers_promises.setImmediate)();
|
|
700
|
+
if (isQuorumQueue && DISABLE_QUORUM_QUEUES_PUBLISH !== "true") {
|
|
701
|
+
await this.assertVHost();
|
|
702
|
+
RabbitMq.validateName("exchange", exchange);
|
|
703
|
+
const channel$1 = await this.assertChannel({ connection: this.publishConnection });
|
|
704
|
+
await this.assertExchange(exchange, this.publishConnection);
|
|
705
|
+
await channel$1.publish(exchange, "", Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
RabbitMq.validateName("exchange", exchange);
|
|
709
|
+
const channel = await this.assertChannelOld({ connection: this.oldPublishConnection });
|
|
710
|
+
await this.assertExchangeOld(exchange, this.oldPublishConnection);
|
|
711
|
+
await channel.publish(exchange, "", Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
|
|
712
|
+
}
|
|
713
|
+
async sendToQueue(queue, content, options, customHeaders, isQuorumQueue = true) {
|
|
714
|
+
if (isQuorumQueue && DISABLE_QUORUM_QUEUES_PUBLISH !== "true") {
|
|
715
|
+
try {
|
|
716
|
+
await this.assertVHost();
|
|
717
|
+
await this.assertChannel({ connection: this.publishConnection });
|
|
718
|
+
} catch (e) {
|
|
719
|
+
this.#logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
|
|
720
|
+
throw e;
|
|
721
|
+
}
|
|
722
|
+
try {
|
|
723
|
+
RabbitMq.validateName("queue", queue);
|
|
724
|
+
await this.assertQueue(queue, options);
|
|
725
|
+
} catch (e) {
|
|
726
|
+
this.#logger.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
|
|
727
|
+
throw e;
|
|
728
|
+
}
|
|
729
|
+
try {
|
|
730
|
+
const res = await this.publishChannel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
|
|
731
|
+
this.#logger.debug(`rabbit: sending to queue ${queue}`, { res });
|
|
732
|
+
return res;
|
|
733
|
+
} catch (e) {
|
|
734
|
+
const isConnected = await this.isConnected();
|
|
735
|
+
this.#logger.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
|
|
736
|
+
throw e;
|
|
737
|
+
}
|
|
738
|
+
} else {
|
|
739
|
+
try {
|
|
740
|
+
await this.assertChannelOld({ connection: this.oldPublishConnection });
|
|
741
|
+
} catch (e) {
|
|
742
|
+
this.#logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
|
|
743
|
+
throw e;
|
|
744
|
+
}
|
|
745
|
+
try {
|
|
746
|
+
RabbitMq.validateName("queue", queue);
|
|
747
|
+
await this.assertQueueOld(queue, options);
|
|
748
|
+
} catch (e) {
|
|
749
|
+
this.#logger.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
|
|
750
|
+
throw e;
|
|
751
|
+
}
|
|
752
|
+
try {
|
|
753
|
+
const res = await this.oldPublishChannel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
|
|
754
|
+
this.#logger.debug(`rabbit: sending to queue ${queue}`, { res });
|
|
755
|
+
return res;
|
|
756
|
+
} catch (e) {
|
|
757
|
+
const isConnected = await this.isConnectedOld();
|
|
758
|
+
this.#logger.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
|
|
759
|
+
throw e;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
async isConnected() {
|
|
764
|
+
let isConnected = true;
|
|
765
|
+
if (!this.gracefulShutdownStarted) {
|
|
766
|
+
if (DISABLE_QUORUM_QUEUES_CONSUME !== "true" || DISABLE_QUORUM_QUEUES_PUBLISH !== "true") {
|
|
767
|
+
this.#logger.debug("rabbit: start is connected");
|
|
768
|
+
const [consumeConnection, publishConnection] = await Promise.all([this.getConnection(this.consumeConnection), this.getConnection(this.publishConnection)]);
|
|
769
|
+
isConnected = consumeConnection.isConnected() && publishConnection.isConnected();
|
|
770
|
+
if (!isConnected) {
|
|
771
|
+
this.#logger.error("rabbit: isConnected - false");
|
|
772
|
+
return false;
|
|
773
|
+
}
|
|
774
|
+
try {
|
|
775
|
+
const unRegisteredConsumers = this.consumersToRegister.filter((c) => !this.consumersTags.get(c.queue));
|
|
776
|
+
if (unRegisteredConsumers.length > 0) {
|
|
777
|
+
const queueNames = unRegisteredConsumers.map((c) => c.queue);
|
|
778
|
+
this.#logger.error("rabbit: found unregistered consumers for queues", {
|
|
779
|
+
count: queueNames.length,
|
|
780
|
+
queues: queueNames
|
|
781
|
+
});
|
|
782
|
+
throw new RabbitError("Found unregistered consumers");
|
|
783
|
+
}
|
|
784
|
+
const channel = await this.assertChannel({ connection: this.publishConnection });
|
|
785
|
+
await channel.waitForConnect();
|
|
786
|
+
await Promise.all(this.consumersToRegister.map((c) => channel.checkQueue(c.queue)));
|
|
787
|
+
} catch (e) {
|
|
788
|
+
this.#logger.error("rabbit: isConnected - false", { msg: e.message });
|
|
789
|
+
return false;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
isConnected = await this.isConnectedOld();
|
|
793
|
+
}
|
|
794
|
+
this.#logger.debug(`rabbit: isConnected - ${isConnected}`);
|
|
795
|
+
return isConnected;
|
|
796
|
+
}
|
|
797
|
+
async gracefulShutdown(signal) {
|
|
798
|
+
this.gracefulShutdownStarted = true;
|
|
799
|
+
const tagsNumber = this.consumersTags.size + this.oldConsumersTags.size;
|
|
800
|
+
this.#logger.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
|
|
801
|
+
const cancelTagPromises = [...this.consumersTags].map(([, { channel, consumerTag }]) => channel.cancel(consumerTag));
|
|
802
|
+
const cancelTagPromisesOld = [...this.oldConsumersTags].map(([, { channel, consumerTag }]) => channel.cancel(consumerTag));
|
|
803
|
+
this.consumersTags.clear();
|
|
804
|
+
this.oldConsumersTags.clear();
|
|
805
|
+
const results = await Promise.allSettled([...cancelTagPromises, ...cancelTagPromisesOld]);
|
|
806
|
+
const rejected = results.filter((p) => p.status === "rejected");
|
|
807
|
+
if (rejected.length > 0) this.#logger.warn(`rabbit: [gracefully-shutdown] #${rejected.length}/${tagsNumber} tags failed to cancel: ${rejected}`);
|
|
808
|
+
else this.#logger.info("rabbit: [gracefully-shutdown] all tags successfully canceled.");
|
|
809
|
+
}
|
|
810
|
+
async consumeFromRabbitOld(queue, callback, options) {
|
|
811
|
+
const optionsWithDefaults = {
|
|
812
|
+
...DEFAULT_OPTIONS,
|
|
813
|
+
...options
|
|
814
|
+
};
|
|
815
|
+
RabbitMq.validateName("queue", queue);
|
|
816
|
+
const uniqueId = (0, node_crypto.randomUUID)();
|
|
817
|
+
const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace } = optionsWithDefaults;
|
|
818
|
+
if (useConsumeWithLock) {
|
|
819
|
+
if (!this.redisLock) throw new RabbitError("Usage of consumeWithLock requires RedisInstance");
|
|
820
|
+
this.#logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
821
|
+
}
|
|
822
|
+
const channel = await this.getNewChannelOld({ connection: this.oldConsumeConnection });
|
|
823
|
+
return channel.addSetup(async (confirmChannel) => {
|
|
824
|
+
await this.assertQueueOld(queue, optionsWithDefaults);
|
|
825
|
+
await confirmChannel.prefetch(limit, false);
|
|
826
|
+
const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
|
|
827
|
+
if (!msg) return null;
|
|
828
|
+
const { [TRACING_HEADER]: traceId, [USER_TRACING_HEADER]: userId, [AUTOMATION_ID_HEADER]: automationId, [__autofleet_zehut.CONTEXTS_IDS_HEADER]: userContextIds } = msg.properties.headers || {};
|
|
829
|
+
const parsedMessage = RabbitMq.parseMsg(msg);
|
|
830
|
+
const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
|
|
831
|
+
const trace = (0, __autofleet_zehut.newTrace)(__autofleet_zehut.traceTypes.RABBIT);
|
|
832
|
+
if (userId && enableRabbitTrace) try {
|
|
833
|
+
await (0, __autofleet_zehut.createOrSetRabbitTrace)(trace, userId, userContextIds);
|
|
834
|
+
} catch (e) {
|
|
835
|
+
this.#logger.error("rabbit: failed to setRabbitTrace", {
|
|
836
|
+
userId,
|
|
837
|
+
e
|
|
838
|
+
});
|
|
839
|
+
return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
|
|
840
|
+
}
|
|
841
|
+
if (traceId) trace.context?.set(TRACING_HEADER, traceId);
|
|
842
|
+
if (auditContext) await auditContext(queue, {
|
|
843
|
+
userId,
|
|
844
|
+
automationId
|
|
845
|
+
});
|
|
846
|
+
const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
|
|
847
|
+
if (!shouldConsume) {
|
|
848
|
+
await this.unlockRedisIfNeeded(releaseLock);
|
|
849
|
+
return this.ack(confirmChannel, msg)(msg);
|
|
850
|
+
}
|
|
851
|
+
let messageAcked = false;
|
|
852
|
+
const localAck = async () => {
|
|
853
|
+
if (messageAcked) return;
|
|
854
|
+
messageAcked = true;
|
|
855
|
+
await this.ack(confirmChannel, msg, true, releaseLock)(msg);
|
|
856
|
+
};
|
|
857
|
+
const localNack = async (_, nackOptions = {}) => {
|
|
858
|
+
if (messageAcked) return;
|
|
859
|
+
this.#logger.debug("rabbit localNack", {
|
|
860
|
+
messageAcked,
|
|
861
|
+
uniqueId,
|
|
862
|
+
deliveryTag: msg.fields.deliveryTag
|
|
863
|
+
});
|
|
864
|
+
messageAcked = true;
|
|
865
|
+
await this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
|
|
866
|
+
};
|
|
867
|
+
try {
|
|
868
|
+
return await callback(parsedMessage, localAck, localNack);
|
|
869
|
+
} catch (e) {
|
|
870
|
+
return localNack(msg);
|
|
871
|
+
}
|
|
872
|
+
}, CONSUMER_DEFAULT_OPTIONS);
|
|
873
|
+
if (!consumerTag) this.#logger.error(`rabbit: failed to consume from queue ${queue} old`);
|
|
874
|
+
else {
|
|
875
|
+
this.#logger.info(`rabbit: adding tag ${consumerTag} to the array old`);
|
|
876
|
+
this.oldConsumersTags.set(queue, {
|
|
877
|
+
channel: confirmChannel,
|
|
878
|
+
consumerTag
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
async getNewChannelOld({ name = rand().toString(), onClose = null, options = {}, connection }) {
|
|
884
|
+
let localConnection;
|
|
885
|
+
try {
|
|
886
|
+
localConnection = await this.getConnectionOld(connection);
|
|
887
|
+
} catch (e) {
|
|
888
|
+
this.#logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
|
|
889
|
+
throw e;
|
|
890
|
+
}
|
|
891
|
+
if (!localConnection) throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
|
|
892
|
+
const channel = localConnection?.createChannel({ ...options });
|
|
893
|
+
(0, node_events.once)(channel, "close").then((args) => {
|
|
894
|
+
this.#logger.error(`rabbit: channel ${name} closed`);
|
|
895
|
+
onClose?.(args);
|
|
896
|
+
});
|
|
897
|
+
try {
|
|
898
|
+
await (0, node_events.once)(channel, "connect");
|
|
899
|
+
this.#logger.debug(`rabbit: channel ${name} CONNECTED`);
|
|
900
|
+
return channel;
|
|
901
|
+
} catch (err) {
|
|
902
|
+
this.#logger.error(`rabbit: channel error ${name} error`, { err });
|
|
903
|
+
throw err;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
async getConnectionOld(connection) {
|
|
907
|
+
const { amqpConnection: connectionLocal, creatingConnection, connectionCreatedEventName, connectionFailedEventName, blockReconnect } = connection;
|
|
908
|
+
if (blockReconnect) {
|
|
909
|
+
this.#logger.debug("rabbit: block reconnect");
|
|
910
|
+
return void 0;
|
|
911
|
+
}
|
|
912
|
+
if (connectionLocal !== null) {
|
|
913
|
+
if (this.options?.disableReconnect || connectionLocal?.isConnected()) {
|
|
914
|
+
this.#logger.debug("rabbit: connection - is connected");
|
|
915
|
+
return connectionLocal;
|
|
916
|
+
}
|
|
917
|
+
this.#logger.debug("rabbit: connection - reconnecting");
|
|
918
|
+
}
|
|
919
|
+
if (creatingConnection) {
|
|
920
|
+
const [event, value] = await Promise.race([connectionCreatedEventName, connectionFailedEventName].map((e) => (0, node_events.once)(this.oldEm, e).then(([result]) => [e, result])));
|
|
921
|
+
if (event === connectionCreatedEventName) return value;
|
|
922
|
+
throw value;
|
|
923
|
+
}
|
|
924
|
+
connection.creatingConnection = true;
|
|
925
|
+
let isResolved = false;
|
|
926
|
+
const findServers = () => {
|
|
927
|
+
const userName = process.env.RABBITMQ_USERNAME || "guest";
|
|
928
|
+
const password = process.env.RABBITMQ_PASSWORD || "guest";
|
|
929
|
+
const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || "localhost";
|
|
930
|
+
this.#logger.debug("rabbit: creating connection", {
|
|
931
|
+
host,
|
|
932
|
+
userName,
|
|
933
|
+
HEARTBEAT
|
|
934
|
+
});
|
|
935
|
+
return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
|
|
936
|
+
};
|
|
937
|
+
const defaultUrls = findServers();
|
|
938
|
+
const newConnection = await (0, amqp_connection_manager.connect)(defaultUrls, { findServers });
|
|
939
|
+
connection.amqpConnection = newConnection;
|
|
940
|
+
const { promise, reject, resolve } = createDeferredPromise();
|
|
941
|
+
newConnection.on("error", (err) => {
|
|
942
|
+
this.#logger.error("rabbit: connection error", { err });
|
|
943
|
+
if (!isResolved) {
|
|
944
|
+
isResolved = true;
|
|
945
|
+
reject(err);
|
|
946
|
+
this.oldEm.emit(connectionFailedEventName, err);
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
newConnection.on("connectFailed", (err) => {
|
|
950
|
+
this.oldConsumersTags.clear();
|
|
951
|
+
if (typeof err.url === "string") err.url = this.maskURL(err.url);
|
|
952
|
+
this.#logger.error("rabbit: connection connectFailed", { err });
|
|
953
|
+
if (!isResolved) {
|
|
954
|
+
isResolved = true;
|
|
955
|
+
reject(err);
|
|
956
|
+
this.oldEm.emit(connectionFailedEventName, err);
|
|
957
|
+
}
|
|
958
|
+
});
|
|
959
|
+
newConnection.on("disconnect", ({ err }) => {
|
|
960
|
+
this.oldConsumersTags.clear();
|
|
961
|
+
this.#logger.debug("rabbit: connection closed");
|
|
962
|
+
if (this.options?.disableReconnect) {
|
|
963
|
+
this.#logger.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
|
|
964
|
+
connection.blockReconnect = true;
|
|
965
|
+
} else this.#logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
966
|
+
});
|
|
967
|
+
newConnection.once("connect", async () => {
|
|
968
|
+
this.#logger.debug("rabbit: connection established");
|
|
969
|
+
connection.creatingConnection = false;
|
|
970
|
+
this.oldEm.emit(connectionCreatedEventName, newConnection);
|
|
971
|
+
isResolved = true;
|
|
972
|
+
resolve(newConnection);
|
|
973
|
+
});
|
|
974
|
+
return promise;
|
|
975
|
+
}
|
|
976
|
+
saveConsumerOld(queue, callback, options) {
|
|
977
|
+
const isConsumerExist = this.oldConsumersToRegister.some((consumer) => consumer.queue === queue);
|
|
978
|
+
if (!isConsumerExist) {
|
|
979
|
+
this.#logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
|
|
980
|
+
this.oldConsumersToRegister.push({
|
|
981
|
+
queue,
|
|
982
|
+
callback,
|
|
983
|
+
options
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
async assertQueueOld(queueName, options) {
|
|
988
|
+
RabbitMq.validateName("queue", queueName);
|
|
989
|
+
if (this.oldQueues[queueName]) {
|
|
990
|
+
delete this.oldQueueSetupPromises[queueName];
|
|
991
|
+
return this.oldQueues[queueName];
|
|
992
|
+
}
|
|
993
|
+
this.oldQueueSetupPromises[queueName] ||= this.setupQueueOld(queueName, options);
|
|
994
|
+
return this.oldQueueSetupPromises[queueName];
|
|
995
|
+
}
|
|
996
|
+
async setupQueueOld(queueName, options) {
|
|
997
|
+
let queue;
|
|
998
|
+
const connection = this.oldPublishConnection;
|
|
999
|
+
const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
|
|
1000
|
+
const localeOptions = {
|
|
1001
|
+
...options,
|
|
1002
|
+
durable: true,
|
|
1003
|
+
arguments: {
|
|
1004
|
+
...options?.arguments,
|
|
1005
|
+
"x-consumer-timeout": 1e3 * 60 * 60 * 24,
|
|
1006
|
+
"x-queue-type": shouldUseQuorum ? "quorum" : "classic"
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
try {
|
|
1010
|
+
const channel = await this.assertChannelOld({ connection });
|
|
1011
|
+
this.#logger.debug("assertQueue->channel.addSetup", { queueName });
|
|
1012
|
+
await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
1013
|
+
this.#logger.debug("assertQueue->channel.assertQueue", { queueName });
|
|
1014
|
+
queue = await channel.assertQueue(queueName, localeOptions);
|
|
1015
|
+
} catch (e) {
|
|
1016
|
+
this.#logger.error("rabbit: assertQueue error", {
|
|
1017
|
+
queueName,
|
|
1018
|
+
options,
|
|
1019
|
+
error: e
|
|
1020
|
+
});
|
|
1021
|
+
if (!this.options?.dontRetryAssert) {
|
|
1022
|
+
this.#logger.debug("retrying assertQueue", { queueName });
|
|
1023
|
+
const channel = await this.assertChannelOld({
|
|
1024
|
+
force: true,
|
|
1025
|
+
connection
|
|
1026
|
+
});
|
|
1027
|
+
await this.deleteQueueOld(queueName, connection);
|
|
1028
|
+
this.#logger.debug("retrying assertQueue->channel.addSetup", { queueName });
|
|
1029
|
+
await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
1030
|
+
this.#logger.debug("retrying assertQueue->channel.assertQueue", { queueName });
|
|
1031
|
+
queue = await channel.assertQueue(queueName, localeOptions);
|
|
1032
|
+
} else throw e;
|
|
1033
|
+
}
|
|
1034
|
+
this.oldQueues[queueName] = queue;
|
|
1035
|
+
return queue;
|
|
1036
|
+
}
|
|
1037
|
+
async assertChannelOld({ force = false, connection }) {
|
|
1038
|
+
if (this.oldPublishChannelSetupPromise) return this.oldPublishChannelSetupPromise;
|
|
1039
|
+
const { promise, resolve, reject } = createDeferredPromise();
|
|
1040
|
+
this.oldPublishChannelSetupPromise = promise;
|
|
1041
|
+
if (this.oldPublishChannel && !force) {
|
|
1042
|
+
resolve(this.oldPublishChannel);
|
|
1043
|
+
return promise;
|
|
1044
|
+
}
|
|
1045
|
+
try {
|
|
1046
|
+
const channel = await this.getNewChannelOld({ connection });
|
|
1047
|
+
channel.on("error", (err) => {
|
|
1048
|
+
this.#logger.error("rabbit: channel error", { err });
|
|
1049
|
+
});
|
|
1050
|
+
if (this.oldPublishConnection === connection) this.oldPublishChannel = channel;
|
|
1051
|
+
resolve(channel);
|
|
1052
|
+
} catch (e) {
|
|
1053
|
+
reject(e);
|
|
1054
|
+
}
|
|
1055
|
+
return promise;
|
|
1056
|
+
}
|
|
1057
|
+
async deleteQueueOld(queue, connection) {
|
|
1058
|
+
RabbitMq.validateName("queue", queue);
|
|
1059
|
+
const channel = await this.assertChannelOld({ connection });
|
|
1060
|
+
this.#logger.info("rabbit: deleting queue", { queue });
|
|
1061
|
+
const deleteQueueRes = await channel.deleteQueue(queue);
|
|
1062
|
+
this.#logger.debug("queue deleted", deleteQueueRes);
|
|
1063
|
+
return deleteQueueRes;
|
|
1064
|
+
}
|
|
1065
|
+
async assertExchangeOld(exchangeName, connection) {
|
|
1066
|
+
const channel = await this.assertChannelOld({ connection });
|
|
1067
|
+
if (this.oldExchanges[exchangeName]) {
|
|
1068
|
+
delete this.oldAssertExchangePromises[exchangeName];
|
|
1069
|
+
return this.oldExchanges[exchangeName];
|
|
1070
|
+
}
|
|
1071
|
+
if (this.oldAssertExchangePromises[exchangeName]) return this.oldAssertExchangePromises[exchangeName];
|
|
1072
|
+
this.oldAssertExchangePromises[exchangeName] = assertExchangeFanout(channel, exchangeName);
|
|
1073
|
+
this.oldExchanges[exchangeName] = await this.oldAssertExchangePromises[exchangeName];
|
|
1074
|
+
return this.oldExchanges[exchangeName];
|
|
1075
|
+
}
|
|
1076
|
+
async isConnectedOld() {
|
|
1077
|
+
if (this.gracefulShutdownStarted) return true;
|
|
1078
|
+
this.#logger.debug("rabbit: start is connected old");
|
|
1079
|
+
const [oldConsumeConnection, oldPublishConnection] = await Promise.all([this.getConnectionOld(this.oldConsumeConnection), this.getConnectionOld(this.oldPublishConnection)]);
|
|
1080
|
+
const isConnected = oldConsumeConnection.isConnected() && oldPublishConnection.isConnected();
|
|
1081
|
+
if (!isConnected) {
|
|
1082
|
+
this.#logger.error("rabbit: isConnected old - false");
|
|
1083
|
+
return false;
|
|
1084
|
+
}
|
|
1085
|
+
try {
|
|
1086
|
+
const unRegisteredConsumersOld = this.consumersToRegister.filter((c) => !this.consumersTags.get(c.queue));
|
|
1087
|
+
if (unRegisteredConsumersOld.length > 0) {
|
|
1088
|
+
const queueNames = unRegisteredConsumersOld.map((c) => c.queue);
|
|
1089
|
+
this.#logger.error("rabbit: found unregistered consumers for queues old", {
|
|
1090
|
+
count: queueNames.length,
|
|
1091
|
+
queues: queueNames
|
|
1092
|
+
});
|
|
1093
|
+
throw new RabbitError("Found unregistered consumers old");
|
|
1094
|
+
}
|
|
1095
|
+
const channelOld = await this.assertChannelOld({ connection: this.oldPublishConnection });
|
|
1096
|
+
await channelOld.waitForConnect();
|
|
1097
|
+
await Promise.all(this.oldConsumersToRegister.map((c) => channelOld.checkQueue(c.queue)));
|
|
1098
|
+
return true;
|
|
1099
|
+
} catch (e) {
|
|
1100
|
+
this.#logger.error("rabbit: isConnected - false old", { msg: e.message });
|
|
1101
|
+
return false;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
maskURL = (url) => {
|
|
1105
|
+
try {
|
|
1106
|
+
const urlObj = new URL(url);
|
|
1107
|
+
urlObj.username = "***";
|
|
1108
|
+
urlObj.password = "***";
|
|
1109
|
+
return urlObj.toString();
|
|
1110
|
+
} catch {
|
|
1111
|
+
return url;
|
|
1112
|
+
}
|
|
1113
|
+
};
|
|
1114
|
+
};
|
|
1115
|
+
var src_default = RabbitMq;
|
|
1116
|
+
|
|
1117
|
+
//#endregion
|
|
1118
|
+
exports.default = src_default;
|
|
1119
|
+
exports.sendCeleryTaskViaHttp = sendCeleryTaskViaHttp;
|
|
1120
|
+
//# sourceMappingURL=index.cjs.map
|