@autofleet/rabbit 5.4.7 → 5.4.9-beta.0
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-DTBo3zxw.d.cts → index-B5CP99aq.d.ts} +26 -6
- package/dist/{index-BjB5zhWf.d.ts → index-COhISXIE.d.cts} +25 -6
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mock/index.d.cts +1 -1
- package/dist/mock/index.d.ts +1 -1
- package/dist/mock/vitest.d.cts +1 -1
- package/dist/mock/vitest.d.ts +1 -1
- package/package.json +3 -3
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import RedisLock from "redis-lock";
|
|
3
3
|
import { AmqpConnectionManager, ChannelWrapper, CreateChannelOpts } from "amqp-connection-manager";
|
|
4
|
-
import
|
|
5
|
-
import { ConfirmChannel, ConsumeMessage, Options, Replies } from "amqplib";
|
|
4
|
+
import "exponential-backoff";
|
|
6
5
|
import { LoggerInstanceManager } from "@autofleet/logger";
|
|
7
6
|
import { createClient } from "redis";
|
|
7
|
+
import { PublishOptions } from "amqp-connection-manager/dist/types/ChannelWrapper";
|
|
8
|
+
import { ConfirmChannel, ConsumeMessage, Options, Replies } from "amqplib";
|
|
8
9
|
|
|
9
10
|
//#region src/lib/redis.d.ts
|
|
10
11
|
interface RedisConfig {
|
|
@@ -12,7 +13,13 @@ interface RedisConfig {
|
|
|
12
13
|
port: number | undefined;
|
|
13
14
|
prefix?: string;
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/lib/consts.d.ts
|
|
18
|
+
|
|
19
|
+
declare const CONNECTION_ROLE: {
|
|
20
|
+
readonly CONSUMER: "consumer";
|
|
21
|
+
readonly PUBLISHER: "publisher";
|
|
22
|
+
};
|
|
16
23
|
//#endregion
|
|
17
24
|
//#region src/lib/types.d.ts
|
|
18
25
|
type ExchangesCache = Record<string, Replies.AssertExchange | undefined>;
|
|
@@ -48,6 +55,7 @@ interface ConnectionData {
|
|
|
48
55
|
connectionCreatedEventName: string;
|
|
49
56
|
connectionFailedEventName: string;
|
|
50
57
|
blockReconnect: boolean;
|
|
58
|
+
connectionRole: typeof CONNECTION_ROLE[keyof typeof CONNECTION_ROLE];
|
|
51
59
|
}
|
|
52
60
|
//#endregion
|
|
53
61
|
//#region src/lib/celery.d.ts
|
|
@@ -77,7 +85,7 @@ interface IAfRabbitMq {
|
|
|
77
85
|
stopAcceptingWork(): Promise<void>;
|
|
78
86
|
drain(timeoutMs?: number, throwOnTimeout?: boolean): Promise<void>;
|
|
79
87
|
startConsume(): Promise<void>;
|
|
80
|
-
redisClient?: ReturnType<typeof
|
|
88
|
+
redisClient?: ReturnType<typeof createClient>;
|
|
81
89
|
}
|
|
82
90
|
interface AfRabbitOptions {
|
|
83
91
|
disableReconnect?: boolean;
|
|
@@ -101,6 +109,16 @@ interface AfRabbitOptions {
|
|
|
101
109
|
* @default true
|
|
102
110
|
*/
|
|
103
111
|
autoStartConsumers?: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Provide an Redis client. When set, redisConfig is ignored.
|
|
114
|
+
*/
|
|
115
|
+
redisClient?: ReturnType<typeof createClient>;
|
|
116
|
+
/**
|
|
117
|
+
* Provide an identifier for the instance, e.g. service name.
|
|
118
|
+
* Used for naming AMQP connections, channels, and consumer tags — makes instances identifiable in the RabbitMQ management UI and logs.
|
|
119
|
+
* @default MY_POD_NAME env var or a random UUID if MY_POD_NAME is not set.
|
|
120
|
+
*/
|
|
121
|
+
identifier?: string;
|
|
104
122
|
}
|
|
105
123
|
interface newChannelOpts {
|
|
106
124
|
name?: string;
|
|
@@ -116,6 +134,7 @@ interface assertChannelOpts {
|
|
|
116
134
|
declare class RabbitMq implements IAfRabbitMq {
|
|
117
135
|
#private;
|
|
118
136
|
readonly options: AfRabbitOptions;
|
|
137
|
+
/** @deprecated Pass redisClient to the options object instead */
|
|
119
138
|
private readonly redisConfig?;
|
|
120
139
|
static parseMsg(msg: ConsumeMessage): Message;
|
|
121
140
|
static validateName(type: string, name: string): void;
|
|
@@ -131,7 +150,7 @@ declare class RabbitMq implements IAfRabbitMq {
|
|
|
131
150
|
readonly queues: QueuesCache;
|
|
132
151
|
readonly queueSetupPromises: QueueSetupPromisesDictionary;
|
|
133
152
|
readonly assertExchangePromises: AssertExchangePromisesDictionary;
|
|
134
|
-
readonly redisClient?: ReturnType<typeof
|
|
153
|
+
readonly redisClient?: ReturnType<typeof createClient>;
|
|
135
154
|
readonly redisLock?: ReturnType<typeof RedisLock>;
|
|
136
155
|
/** A map of consumers' tags used for canceling consumption */
|
|
137
156
|
readonly consumersTags: Map<string, {
|
|
@@ -160,6 +179,7 @@ declare class RabbitMq implements IAfRabbitMq {
|
|
|
160
179
|
consumerTag: string;
|
|
161
180
|
}>;
|
|
162
181
|
private readonly oldConsumersToRegister;
|
|
182
|
+
readonly identifier: string;
|
|
163
183
|
constructor(options?: AfRabbitOptions, redisConfig?: RedisConfig | undefined);
|
|
164
184
|
private getRedisKey;
|
|
165
185
|
private assertVHost;
|
|
@@ -241,4 +261,4 @@ declare class RabbitMq implements IAfRabbitMq {
|
|
|
241
261
|
type ConsumerCallbackFunction = CallbackFunction;
|
|
242
262
|
//#endregion
|
|
243
263
|
export { sendCeleryTaskViaHttp as a, RabbitMq as i, ConsumerCallbackFunction as n, IAfRabbitMq as r, AfRabbitOptions as t };
|
|
244
|
-
//# sourceMappingURL=index-
|
|
264
|
+
//# sourceMappingURL=index-B5CP99aq.d.ts.map
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import RedisLock from "redis-lock";
|
|
3
3
|
import { AmqpConnectionManager, ChannelWrapper, CreateChannelOpts } from "amqp-connection-manager";
|
|
4
|
-
import { LoggerInstanceManager } from "@autofleet/logger";
|
|
5
|
-
import { createClient } from "redis";
|
|
6
4
|
import { PublishOptions } from "amqp-connection-manager/dist/types/ChannelWrapper";
|
|
7
5
|
import { ConfirmChannel, ConsumeMessage, Options, Replies } from "amqplib";
|
|
6
|
+
import { createClient } from "redis";
|
|
7
|
+
import { LoggerInstanceManager } from "@autofleet/logger";
|
|
8
8
|
|
|
9
9
|
//#region src/lib/redis.d.ts
|
|
10
10
|
interface RedisConfig {
|
|
@@ -12,7 +12,13 @@ interface RedisConfig {
|
|
|
12
12
|
port: number | undefined;
|
|
13
13
|
prefix?: string;
|
|
14
14
|
}
|
|
15
|
-
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/lib/consts.d.ts
|
|
17
|
+
|
|
18
|
+
declare const CONNECTION_ROLE: {
|
|
19
|
+
readonly CONSUMER: "consumer";
|
|
20
|
+
readonly PUBLISHER: "publisher";
|
|
21
|
+
};
|
|
16
22
|
//#endregion
|
|
17
23
|
//#region src/lib/types.d.ts
|
|
18
24
|
type ExchangesCache = Record<string, Replies.AssertExchange | undefined>;
|
|
@@ -48,6 +54,7 @@ interface ConnectionData {
|
|
|
48
54
|
connectionCreatedEventName: string;
|
|
49
55
|
connectionFailedEventName: string;
|
|
50
56
|
blockReconnect: boolean;
|
|
57
|
+
connectionRole: typeof CONNECTION_ROLE[keyof typeof CONNECTION_ROLE];
|
|
51
58
|
}
|
|
52
59
|
//#endregion
|
|
53
60
|
//#region src/lib/celery.d.ts
|
|
@@ -77,7 +84,7 @@ interface IAfRabbitMq {
|
|
|
77
84
|
stopAcceptingWork(): Promise<void>;
|
|
78
85
|
drain(timeoutMs?: number, throwOnTimeout?: boolean): Promise<void>;
|
|
79
86
|
startConsume(): Promise<void>;
|
|
80
|
-
redisClient?: ReturnType<typeof
|
|
87
|
+
redisClient?: ReturnType<typeof createClient>;
|
|
81
88
|
}
|
|
82
89
|
interface AfRabbitOptions {
|
|
83
90
|
disableReconnect?: boolean;
|
|
@@ -101,6 +108,16 @@ interface AfRabbitOptions {
|
|
|
101
108
|
* @default true
|
|
102
109
|
*/
|
|
103
110
|
autoStartConsumers?: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Provide an Redis client. When set, redisConfig is ignored.
|
|
113
|
+
*/
|
|
114
|
+
redisClient?: ReturnType<typeof createClient>;
|
|
115
|
+
/**
|
|
116
|
+
* Provide an identifier for the instance, e.g. service name.
|
|
117
|
+
* Used for naming AMQP connections, channels, and consumer tags — makes instances identifiable in the RabbitMQ management UI and logs.
|
|
118
|
+
* @default MY_POD_NAME env var or a random UUID if MY_POD_NAME is not set.
|
|
119
|
+
*/
|
|
120
|
+
identifier?: string;
|
|
104
121
|
}
|
|
105
122
|
interface newChannelOpts {
|
|
106
123
|
name?: string;
|
|
@@ -116,6 +133,7 @@ interface assertChannelOpts {
|
|
|
116
133
|
declare class RabbitMq implements IAfRabbitMq {
|
|
117
134
|
#private;
|
|
118
135
|
readonly options: AfRabbitOptions;
|
|
136
|
+
/** @deprecated Pass redisClient to the options object instead */
|
|
119
137
|
private readonly redisConfig?;
|
|
120
138
|
static parseMsg(msg: ConsumeMessage): Message;
|
|
121
139
|
static validateName(type: string, name: string): void;
|
|
@@ -131,7 +149,7 @@ declare class RabbitMq implements IAfRabbitMq {
|
|
|
131
149
|
readonly queues: QueuesCache;
|
|
132
150
|
readonly queueSetupPromises: QueueSetupPromisesDictionary;
|
|
133
151
|
readonly assertExchangePromises: AssertExchangePromisesDictionary;
|
|
134
|
-
readonly redisClient?: ReturnType<typeof
|
|
152
|
+
readonly redisClient?: ReturnType<typeof createClient>;
|
|
135
153
|
readonly redisLock?: ReturnType<typeof RedisLock>;
|
|
136
154
|
/** A map of consumers' tags used for canceling consumption */
|
|
137
155
|
readonly consumersTags: Map<string, {
|
|
@@ -160,6 +178,7 @@ declare class RabbitMq implements IAfRabbitMq {
|
|
|
160
178
|
consumerTag: string;
|
|
161
179
|
}>;
|
|
162
180
|
private readonly oldConsumersToRegister;
|
|
181
|
+
readonly identifier: string;
|
|
163
182
|
constructor(options?: AfRabbitOptions, redisConfig?: RedisConfig | undefined);
|
|
164
183
|
private getRedisKey;
|
|
165
184
|
private assertVHost;
|
|
@@ -241,4 +260,4 @@ declare class RabbitMq implements IAfRabbitMq {
|
|
|
241
260
|
type ConsumerCallbackFunction = CallbackFunction;
|
|
242
261
|
//#endregion
|
|
243
262
|
export { sendCeleryTaskViaHttp as a, RabbitMq as i, ConsumerCallbackFunction as n, IAfRabbitMq as r, AfRabbitOptions as t };
|
|
244
|
-
//# sourceMappingURL=index-
|
|
263
|
+
//# sourceMappingURL=index-COhISXIE.d.cts.map
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,`__esModule`,{value:!0});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`node:process`),l=require(`node:timers/promises`),u=require(`node:events`),d=require(`moment`);d=s(d);let f=require(`redis-lock`);f=s(f);let p=require(`amqp-connection-manager`),m=require(`exponential-backoff`),h=require(`@autofleet/zehut`),g=require(`node:crypto`),_=require(`@autofleet/logger`);_=s(_);let v=require(`redis`);const y=(0,_.default)();var b=y,x=class extends Error{constructor(e){super(e),this.name=`RabbitError`}};const S=e=>(0,v.createClient)({socket:e});var C=S;const w=6e4*60*12,T=1e3*5,E=`x-retry-count`,D=`x-trace-id`,O=`x-af-user-id`,k=`x-af-automation-id`,A=`x-total-processing-duration-ms`,j=`x-redelivered-count`,M=!1,N={limit:1,retries:1,deadMessageTtl:432e5,lockTimeout:T,useConsumeWithLock:!1,auditContext:null,enableRabbitTrace:!1,callNackOnAbort:!1},P={startingDelay:500,timeMultiple:4,numOfAttempts:5},F={startingDelay:1,timeMultiple:1,numOfAttempts:5},{PROJECT_ID:I}=process.env,L=async(e,t)=>e.assertExchange(t,`fanout`),R=()=>Math.floor(Math.random()*1e5),z=()=>{if(Promise.withResolvers)return Promise.withResolvers();let e,t;return{promise:new Promise((n,r)=>{e=n,t=r}),resolve:e,reject:t}},B=()=>[`af-experiment-manager`,`dev1-experiment-manager`].includes(I||``),V=()=>B()?P:F,H=`ha-promote-on-failure`,U=`ha-promote-on-shutdown`,W={arguments:{"ha-promote-on-failure":`always`,"ha-promote-on-shutdown":`always`}},G={host:process.env.RABBITMQ_SERVICE_HOST||`localhost`,username:process.env.RABBITMQ_USERNAME||`guest`,password:process.env.RABBITMQ_PASSWORD||`guest`};async function K(e,{taskName:t,queueName:n}){let r=`http://${G.host}:15672/api/exchanges/%2f/amq.default/publish`,i={task:t,id:(0,g.randomUUID)(),args:[e]},a={properties:{delivery_mode:2,content_type:`application/json`},routing_key:n,payload:JSON.stringify(i),payload_encoding:`string`};try{let e=await fetch(r,{method:`POST`,headers:{"Content-Type":`application/json`,Authorization:`Basic ${Buffer.from(`${G.username}:${G.password}`).toString(`base64`)}`},body:JSON.stringify(a)});if(e.ok){let t=await e.json();b.info(`Successfully published message:`,t)}else b.error(`Failed to publish message. Status code: ${e.status}`),b.error(`Response: ${await e.text()}`)}catch(e){throw b.error(`Error sending request:`,e instanceof Error?e.message:String(e)),e}}const q=1e3*10,{DISABLE_QUORUM_QUEUES_CONSUME:J,DISABLE_QUORUM_QUEUES_PUBLISH:Y}=c.env,X=`60`;var Z=class e{static parseMsg(e){let t=e.content.toString();try{t=JSON.parse(t)}catch{}return{...e,content:t}}static validateName(e,t){if(!t||t===``)throw new x(`error while using ${e} with no name`)}static getPublishOptions(e={}){let t=(0,h.getUser)(),n=h.outbreak.getCurrentContextTraceId();return{timestamp:(0,d.default)().unix(),timeout:1e4,headers:{creationTimestamp:(0,d.default)().valueOf(),...e,...t?.id===void 0?{}:{[O]:t.id},...t?.contextIds===void 0?{}:{[h.CONTEXTS_IDS_HEADER]:t.contextIds},...n===void 0?{}:{[D]:n}}}}#e=new Set;#t;constructor(t={},n){this.options=t,this.redisConfig=n,this.DISCONNECT_MSG=`rabbit: connection disconnect`,this.RECONNECT_MSG=`rabbit: connection disconnect - reconnecting`,this.publishChannel=null,this.publishChannelSetupPromise=null,this.publishConnection={amqpConnection:null,creatingConnection:!1,connectionCreatedEventName:`publishConnectionCreated`,connectionFailedEventName:`publishConnectionFailed`,blockReconnect:!1},this.consumeConnection={amqpConnection:null,creatingConnection:!1,connectionCreatedEventName:`consumeConnectionCreated`,connectionFailedEventName:`consumeConnectionFailed`,blockReconnect:!1},this.em=new u.EventEmitter,this.exchanges={},this.queues={},this.queueSetupPromises={},this.assertExchangePromises={},this.consumersTags=new Map,this.consumersToRegister=[],this.pendingConsumers=[],this.doesVHostExist=!1,this.vhost=`quorum-vhost`,this.gracefulShutdownStarted=!1,this.activeCallbackCount=0,this.shutdownAbortController=new AbortController,this.oldPublishChannel=null,this.oldPublishChannelSetupPromise=null,this.oldPublishConnection={amqpConnection:null,creatingConnection:!1,connectionCreatedEventName:`oldPublishConnectionCreated`,connectionFailedEventName:`oldPublishConnectionFailed`,blockReconnect:!1},this.oldConsumeConnection={amqpConnection:null,creatingConnection:!1,connectionCreatedEventName:`oldConsumeConnectionCreated`,connectionFailedEventName:`oldConsumeConnectionFailed`,blockReconnect:!1},this.oldEm=new u.EventEmitter,this.oldExchanges={},this.oldQueues={},this.oldQueueSetupPromises={},this.oldAssertExchangePromises={},this.oldConsumersTags=new Map,this.oldConsumersToRegister=[],this.assertVHost=async()=>{if(this.doesVHostExist)return;let e=process.env.RABBITMQ_USERNAME||`guest`,t=process.env.RABBITMQ_PASSWORD||`guest`,n={Authorization:`Basic ${Buffer.from(`${e}:${t}`).toString(`base64`)}`,"Content-Type":`application/json`},r=`${`http://${(this.options?.rabbitHost||process.env.RABBITMQ_SERVICE_HOST||`localhost`).split(`:`)[0]}:15672`}/api/vhosts/${encodeURIComponent(this.vhost)}`;try{let e=await fetch(r,{method:`GET`,headers:n});if(e.status===200){this.doesVHostExist=!0,this.#t.info(`Vhost exists`,{vhost:this.vhost});return}if(e.status!==404)throw this.#t.error(`Failed to check vhost`,{response:e}),new x(`Failed to check vhost`);let t=await fetch(r,{method:`PUT`,headers:n,body:JSON.stringify({default_queue_type:`quorum`})});if(!t.ok)throw this.#t.error(`Failed to create vhost`,{response:t}),new x(`Failed to create vhost`);this.doesVHostExist=!0,this.#t.info(`Vhost created`,{vhost:this.vhost})}catch(e){throw this.#t.error(`Failed to check or create vhost`,{error:e}),e}},this.shouldConsumeMessageByTimestamp=async e=>{if(!e)return!1;let{headers:t}=e.properties,n=t?.creationTimestamp;if(n&&t?.redisTimestampValidationKey&&this.redisClient){let e=this.getRedisKey(t.redisTimestampValidationKey),r=await this.redisClient.get(e);return!r||parseInt(r,10)<=parseInt(n,10)}return!0},this.ack=(e,t,n=!1,r=null)=>async i=>{if(!t)return;this.#t.debug(`rabbit acking message`,{deliveryTag:t.fields.deliveryTag}),await e.ack(t);let{headers:a}=t.properties,o=a?.creationTimestamp;if(n&&o&&a?.redisTimestampValidationKey&&this.redisClient){let e=parseInt(o,10),t=this.getRedisKey(a.redisTimestampValidationKey);await this.redisClient.set(t,e,{EX:3600}),await this.unlockRedisIfNeeded(r)}},this.nack=(t,n,r,i,a,o)=>async(s,{skipRetry:c=!1,consumptionDuration:l,isAborted:u=!1}={})=>{if(await this.unlockRedisIfNeeded(o),!t||!a){this.#t.error(`no channel or msg`,{msg:a});return}let d=this.getRetriesCount(a),f=this.getTotalConsumptionDuration(a)??0,p=this.getRedeliveredCount(a),m=f+(l??0),h=(c||d>=r.retries)&&!u;this.#t.info(`nack message`,{deliveryTag:a.fields.deliveryTag,currentRetryCount:d,updatedConsumptionDuration:m,sendToDeadQueue:h,isAborted:u}),await this.sendToQueue(`${n}${h?`-dead`:``}`,e.parseMsg(a).content,h?i:r,{...a.properties.headers,[E]:u?d:d+1,[A]:m,[j]:u?p+1:p},r.isQuorumQueue),this.#t.debug(`rabbit nacking message`,{deliveryTag:a.fields.deliveryTag}),await t.ack(a)},this.maskURL=e=>{try{let t=new URL(e);return t.username=`***`,t.password=`***`,t.toString()}catch{return e}},this.#t=t?.logger??b,n&&(this.redisClient=C(n).on(`error`,e=>{this.#t.error(`rabbit: Redis error`,{err:e,redisConfig:n})}),this.redisClient.connect().catch(e=>{this.#t.error(`rabbit: Failed to connect to Redis`,{err:e,redisConfig:n})}),this.redisLock=(0,f.default)(this.redisClient)),this.#t.info(`rabbit: [gracefully-shutdown] adding gracefully shutdown for process.pid ${process.pid}`),this.options?.dontGracefulShutdown||(process.on(`SIGTERM`,async()=>{await this.gracefulShutdown(`SIGTERM`)}),process.on(`SIGINT`,async()=>{await this.gracefulShutdown(`SIGINT`)}))}getRedisKey(e){return`${this.redisConfig?.prefix||``}${e}`}getRetriesCount(e){let t=e.properties.headers?.[E];return Number.parseInt(t||`0`,10)||0}getRedeliveredCount(e){let t=e.properties.headers?.[j];return Number.parseInt(t||`0`,10)||0}getTotalConsumptionDuration(e){let t=e.properties.headers?.[A];return t?Number.parseInt(t,10):0}async getConnection(e){let{amqpConnection:t,creatingConnection:n,connectionCreatedEventName:r,connectionFailedEventName:i,blockReconnect:a}=e;if(a){this.#t.debug(`rabbit: block reconnect`);return}if(t!==null){if(this.options?.disableReconnect||t?.isConnected())return this.#t.debug(`rabbit: connection - is connected`),t;this.#t.debug(`rabbit: connection - reconnecting`)}if(n){this.#t.debug(`rabbit: creating connection emi`);let[e,t]=await Promise.race([r,i].map(e=>(0,u.once)(this.em,e).then(([t])=>[e,t])));if(e===r)return t;throw t}e.creatingConnection=!0;let o=!1,s=()=>{let e=process.env.RABBITMQ_USERNAME||`guest`,t=process.env.RABBITMQ_PASSWORD||`guest`,n=this.options?.rabbitHost||process.env.RABBITMQ_SERVICE_HOST||`localhost`;return this.#t.debug(`rabbit: creating connection`,{host:n,userName:e,HEARTBEAT:`60`}),[`amqp://${e}:${t}@${n}/${this.vhost}?heartbeat=60`]},c=(0,p.connect)(s(),{findServers:s});e.amqpConnection=c;let{promise:l,reject:d,resolve:f}=z();return c.on(`error`,e=>{this.#t.error(`rabbit: connection error`,{err:e}),o||(o=!0,d(e),this.em.emit(i,e))}),c.on(`connectFailed`,e=>{this.consumersTags.clear(),typeof e.url==`string`&&(e.url=this.maskURL(e.url)),this.#t.error(`rabbit: connection connectFailed`,{err:e,advice:`Check if the vhost exist`,vhost:this.vhost}),o||(o=!0,d(e),this.em.emit(i,e))}),c.on(`disconnect`,({err:t})=>{this.consumersTags.clear(),this.#t.debug(`rabbit: connection closed`),this.options?.disableReconnect?(this.#t.error(`${this.DISCONNECT_MSG}${t&&` - ${t}`}`),e.blockReconnect=!0):this.#t.error(`${this.RECONNECT_MSG}${t&&` - ${t}`}`)}),c.once(`connect`,async()=>{this.#t.debug(`rabbit: connection established`),e.creatingConnection=!1,this.em.emit(r,c),o=!0,f(c)}),l}async getNewChannel({name:e=R().toString(),onClose:t=null,options:n={},connection:r}){let i;try{i=await this.getConnection(r)}catch(t){throw this.#t.error(`rabbit: error on get connection for new channel ${e} `,{e:t}),t}let a=i?.createChannel({...n});(0,u.once)(a,`close`).then(n=>{this.#t.error(`rabbit: channel ${e} closed`),t?.(n)});try{return await(0,u.once)(a,`connect`),this.#t.debug(`rabbit: channel ${e} CONNECTED`),a}catch(t){throw this.#t.error(`rabbit: channel error ${e} error`,{err:t}),t}}async assertChannel({force:e=!1,connection:t}){if(this.publishChannel&&!e)return this.publishChannel;if(this.publishChannelSetupPromise)return this.publishChannelSetupPromise;let{promise:n,resolve:r,reject:i}=z();this.publishChannelSetupPromise=n;try{let e=await this.getNewChannel({connection:t});e.on(`error`,e=>{this.#t.error(`rabbit: channel error`,{err:e})}),this.publishConnection===t&&(this.publishChannel=e),r(e)}catch(e){i(e)}return n}async assertExchange(e,t){let n=await this.assertChannel({connection:t});return this.exchanges[e]?(delete this.assertExchangePromises[e],this.exchanges[e]):this.assertExchangePromises[e]?this.assertExchangePromises[e]:(this.assertExchangePromises[e]=L(n,e),this.exchanges[e]=await this.assertExchangePromises[e],this.exchanges[e])}async getQueueLength(t){e.validateName(`queue`,t);let{publishChannel:n}=this;if(!n)throw new x(`channel is not defined`);return this.#t.debug(`rabbit: getting queue length`,{queue:t,connected:this.publishConnection.amqpConnection?.isConnected()}),n?.checkQueue(t)}async deleteQueue(t,n){e.validateName(`queue`,t);let r=await this.assertChannel({connection:n});this.#t.info(`rabbit: deleting queue`,{queue:t});let i=await r.deleteQueue(t);return this.#t.debug(`queue deleted`,i),i}async bindQueue(e,t){let n=await this.assertChannel({connection:this.publishConnection});return await n.addSetup(n=>n.bindQueue(e,t,``)),n.bindQueue(e,t,``)}async setupQueue(e,t){let n,r=this.publishConnection,i={...t,durable:!0,arguments:{...t?.arguments,"x-consumer-timeout":1e3*60*60*24,"x-queue-type":`quorum`}};try{let t=await this.assertChannel({connection:r});this.#t.debug(`assertQueue->channel.addSetup`,{queueName:e}),await t.addSetup(async t=>{await t.assertQueue(e,i)}),this.#t.debug(`assertQueue->channel.assertQueue`,{queueName:e}),n=await t.assertQueue(e,i)}catch(a){if(this.#t.error(`rabbit: assertQueue error`,{queueName:e,options:t,error:a}),this.options?.dontRetryAssert)throw a;{this.#t.debug(`retrying assertQueue`,{queueName:e});let t=await this.assertChannel({force:!0,connection:r});await this.deleteQueue(e,r),this.#t.debug(`retrying assertQueue->channel.addSetup`,{queueName:e}),await t.addSetup(t=>t.assertQueue(e,i)),this.#t.debug(`retrying assertQueue->channel.assertQueue`,{queueName:e}),n=await t.assertQueue(e,i)}}return this.queues[e]=n,n}static shouldUseQuorum(e){let t=process.env.QUORUM_QUEUES_WHITELIST;return t===`*`?!0:t?t.split(`,`).includes(e):!1}async assertQueue(t,n){return e.validateName(`queue`,t),this.queues[t]?(delete this.queueSetupPromises[t],this.queues[t]):(this.queueSetupPromises[t]??=this.setupQueue(t,n),this.queueSetupPromises[t])}saveConsumer(e,t,n){this.consumersToRegister.some(t=>t.queue===e)||(this.#t.info(`rabbit: consumer: ${e} saved in consumer array`),this.consumersToRegister.push({queue:e,callback:t,options:n}))}async consume(e,t,n){let r=async()=>{this.saveConsumerOld(e,t,n),n?.isQuorumQueue!==!1&&J!==`true`&&(this.saveConsumer(e,t,n),await(0,m.backOff)(()=>this.assertVHost(),V()),await this.consumeNew(e,t,n)),await this.consumeOld(e,t,n)};this.options.autoStartConsumers===!1?this.pendingConsumers.push(r):await r()}async consumeNew(e,t,n){await this.consumeFromRabbit(e,t,n)}async consumeOld(e,t,n){await this.consumeFromRabbitOld(e,t,n)}async lockRedisIfNeeded(e,t){let{properties:{headers:n}}=e,r=n?.creationTimestamp,i=null;return t.useConsumeWithLock&&r&&n?.redisTimestampValidationKey&&this.redisLock&&(i=await this.redisLock(n.redisTimestampValidationKey,t?.lockTimeout||T)),i}async unlockRedisIfNeeded(e){this.redisLock&&e&&await e()}async consumeFromRabbit(t,n,r){let i={...N,...r};e.validateName(`queue`,t);let a=(0,g.randomUUID)(),{limit:o,deadMessageTtl:s,useConsumeWithLock:c,lockTimeout:l,auditContext:d,enableRabbitTrace:f}=i;if(c){if(!this.redisLock)throw new x(`Usage of consumeWithLock requires RedisInstance`);this.#t.info(`rabbit: Consuming with lock from queue ${t} with lockTimeout: ${l}ms`)}return(await this.getNewChannel({connection:this.consumeConnection})).addSetup(async c=>{await this.assertQueue(t,i),await c.prefetch(o,!1);let{consumerTag:l}=await c.consume(t,async o=>{if(!o)return null;let l=Date.now(),p=()=>Date.now()-l,{[D]:m,[O]:g,[k]:_,[h.CONTEXTS_IDS_HEADER]:v}=o.properties.headers??{},y=e.parseMsg(o),b=await this.lockRedisIfNeeded(y,i),x=(0,h.newTrace)(h.traceTypes.RABBIT);if(g&&f)try{await(0,h.createOrSetRabbitTrace)(x,g,v)}catch(e){return this.#t.error(`rabbit: failed to setRabbitTrace`,{userId:g,e}),this.nack(c,t,i,{messageTtl:s},o,b)(o)}if(m&&x.context?.set(D,m),d&&await d(t,{userId:g,automationId:_}),!await this.shouldConsumeMessageByTimestamp(y))return await this.unlockRedisIfNeeded(b),this.ack(c,o)(o);let S=!1,C=async()=>{S||(S=!0,await this.ack(c,o,!0,b)(o))},w=async(e,n={})=>{if(S)return;this.#t.debug(`rabbit localNack`,{messageAcked:S,uniqueId:a,deliveryTag:o.fields.deliveryTag}),S=!0;let r=p();await this.nack(c,t,i,{messageTtl:s},o,b)(o,{...n,consumptionDuration:r})};this.activeCallbackCount++;let T=null;try{return r?.callNackOnAbort&&(T=(0,u.addAbortListener)(this.shutdownAbortController.signal,()=>{this.#t.warn(`rabbit: callback aborted`,{uniqueId:a,deliveryTag:o.fields.deliveryTag});let e=w(o,{isAborted:!0});this.#e.add(e),e.finally(()=>{this.#e.delete(e)})})),await n(y,C,w,this.shutdownAbortController.signal)}catch{return await w(o)}finally{this.activeCallbackCount--,T&&T[Symbol.dispose]()}},W);l?(this.#t.info(`rabbit: adding tag ${l} to the array.`),this.consumersTags.set(t,{channel:c,consumerTag:l})):this.#t.error(`rabbit: failed to consume from queue ${t}`)})}async consumeFromExchange(t,n,r,i){let a={...N,...i};e.validateName(`exchange`,n),e.validateName(`queue`,t);let{limit:o}=a,s=async()=>{i?.isQuorumQueue!==!1&&J!==`true`&&(await this.saveConsumer(t,r,i),await(0,m.backOff)(()=>this.assertVHost(),V()),await(await this.getNewChannel({name:`consume-exchange-${n}-queue-${t}`,connection:this.consumeConnection})).addSetup(async e=>{let a=await L(e,n);return await e.assertQueue(t),this.exchanges[n]=a,await e.prefetch(o,!1),Promise.all([e.bindQueue(t,n,``),this.consumeNew(t,r,i)])})),await this.saveConsumerOld(t,r,i),await(await this.getNewChannelOld({name:`consume-exchange-${n}-queue-${t}-old`,connection:this.oldConsumeConnection})).addSetup(async e=>{let a=await L(e,n);return await e.assertQueue(t),this.oldExchanges[n]=a,await e.prefetch(o,!1),Promise.all([e.bindQueue(t,n,``),this.consumeOld(t,r,i)])})};this.options.autoStartConsumers===!1?this.pendingConsumers.push(s):await s()}async startConsume(){await Promise.all(this.pendingConsumers.map(e=>e())),this.pendingConsumers.length=0}async publish(t,n,r,i=!0){if(await(0,l.setImmediate)(),i&&Y!==`true`){await this.assertVHost(),e.validateName(`exchange`,t);let i=await this.assertChannel({connection:this.publishConnection});await this.assertExchange(t,this.publishConnection),await i.publish(t,``,Buffer.from(JSON.stringify(n)),e.getPublishOptions(r));return}e.validateName(`exchange`,t);let a=await this.assertChannelOld({connection:this.oldPublishConnection});await this.assertExchangeOld(t,this.oldPublishConnection),await a.publish(t,``,Buffer.from(JSON.stringify(n)),e.getPublishOptions(r))}async sendToQueue(t,n,r,i,a=!0){if(a&&Y!==`true`){try{await this.assertVHost(),await this.assertChannel({connection:this.publishConnection})}catch(e){throw this.#t.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${t}`,{e}),e}try{e.validateName(`queue`,t),await this.assertQueue(t,r)}catch(e){throw this.#t.error(`rabbit sendToQueue: failed to assert queue ${t}`,{e}),e}try{let r=await this.publishChannel?.sendToQueue(t,Buffer.from(JSON.stringify(n)),e.getPublishOptions(i));return this.#t.debug(`rabbit: sending to queue ${t}`,{res:r}),r}catch(e){let n=await this.isConnected();throw this.#t.error(`rabbit sendToQueue: failed to send to queue ${t}, isConnected: ${n}`,{e}),e}}else{try{await this.assertChannelOld({connection:this.oldPublishConnection})}catch(e){throw this.#t.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${t}`,{e}),e}try{e.validateName(`queue`,t),await this.assertQueueOld(t,r)}catch(e){throw this.#t.error(`rabbit sendToQueue: failed to assert queue ${t}`,{e}),e}try{let r=await this.oldPublishChannel?.sendToQueue(t,Buffer.from(JSON.stringify(n)),e.getPublishOptions(i));return this.#t.debug(`rabbit: sending to queue ${t}`,{res:r}),r}catch(e){let n=await this.isConnectedOld();throw this.#t.error(`rabbit sendToQueue: failed to send to queue ${t}, isConnected: ${n}`,{e}),e}}}async isConnected(){let e=!0;if(!this.gracefulShutdownStarted){if(J!==`true`||Y!==`true`){this.#t.debug(`rabbit: start is connected`);let[t,n]=await Promise.all([this.getConnection(this.consumeConnection),this.getConnection(this.publishConnection)]);if(e=t.isConnected()&&n.isConnected(),!e)return this.#t.error(`rabbit: isConnected - false`),!1;try{let e=this.consumersToRegister.filter(e=>!this.consumersTags.get(e.queue));if(e.length>0){let t=e.map(e=>e.queue);throw this.#t.error(`rabbit: found unregistered consumers for queues`,{count:t.length,queues:t}),new x(`Found unregistered consumers`)}let t=await this.assertChannel({connection:this.publishConnection});await t.waitForConnect(),await Promise.all(this.consumersToRegister.map(e=>t.checkQueue(e.queue)))}catch(e){return this.#t.error(`rabbit: isConnected - false`,{msg:e.message}),!1}}e=await this.isConnectedOld()}return this.#t.debug(`rabbit: isConnected - ${e}`),e}async stopAcceptingWork(){this.#t.info(`rabbit: stop accepting work - canceling all tags`);let e=[...this.consumersTags].map(([,{channel:e,consumerTag:t}])=>e.cancel(t)),t=[...this.oldConsumersTags].map(([,{channel:e,consumerTag:t}])=>e.cancel(t));this.consumersTags.clear(),this.oldConsumersTags.clear();let n=(await Promise.allSettled([...e,...t])).filter(e=>e.status===`rejected`);n.length>0?this.#t.warn(`rabbit: stop accepting work - failed to cancel #${n.length} tags: ${n}`):this.#t.info(`rabbit: stop accepting work - all tags successfully canceled.`)}async drain(e=1e4,t=!0){let n=Date.now(),r=this.activeCallbackCount;if(this.#t.info(`rabbit: [drain] waiting for active callbacks`,{activeCallbackCount:r,timeoutMs:e}),r===0){this.#t.info(`rabbit: [drain] no active callbacks to drain`);return}let i=this.publishChannel?.queueLength()??0;for(i>0&&this.#t.warn(`rabbit: [drain] there are still published messages waiting in the channel buffer`,{publishedMessagesWaiting:i});this.activeCallbackCount>0;){let r=Date.now()-n;if(r>=e){let n=this.activeCallbackCount;if(this.#t.warn(`rabbit: [drain] timeout waiting for callbacks`,{duration:r,remainingCallbacks:n,timeoutMs:e}),this.shutdownAbortController.abort(Error(`Graceful shutdown initiated by drain timeout after ${r}ms with ${n} callbacks still active`)),await Promise.allSettled([...this.#e]),t)throw Error(`Drain timeout after ${e}ms - ${n} callbacks still active`);return}await new Promise(e=>setTimeout(e,50))}let a=Date.now()-n;this.#t.info(`rabbit: [drain] all callbacks completed`,{duration:a,callbackCount:r})}async closeConnections(){if(this.activeCallbackCount>0){this.#t.warn(`rabbit: closing connections received with active callbacks. not performing the close`,{activeCallbackCount:this.activeCallbackCount});return}this.#t.info(`rabbit: closing connections`);let e=[this.consumeConnection,this.publishConnection,this.oldConsumeConnection,this.oldPublishConnection];await Promise.allSettled(e.map(async e=>{if(e.amqpConnection)try{await e.amqpConnection.close(),this.#t.info(`rabbit: connection closed successfully`)}catch(e){this.#t.error(`rabbit: error closing connection`,{e})}}))}async gracefulShutdown(e){this.gracefulShutdownStarted=!0;let t=this.consumersTags.size+this.oldConsumersTags.size;this.#t.info(`rabbit: [gracefully-shutdown] received ${e}! canceling #${t} tags...`),await this.stopAcceptingWork(),this.shutdownAbortController.abort(Error(`Graceful shutdown initiated by signal: ${e}`))}async consumeFromRabbitOld(t,n,r){let i={...N,...r};e.validateName(`queue`,t);let a=(0,g.randomUUID)(),{limit:o,deadMessageTtl:s,useConsumeWithLock:c,lockTimeout:l,auditContext:d,enableRabbitTrace:f,callNackOnAbort:p}=i;if(c){if(!this.redisLock)throw new x(`Usage of consumeWithLock requires RedisInstance`);this.#t.info(`rabbit: Consuming with lock from queue ${t} with lockTimeout: ${l}ms`)}return(await this.getNewChannelOld({connection:this.oldConsumeConnection})).addSetup(async r=>{await this.assertQueueOld(t,i),await r.prefetch(o,!1);let{consumerTag:c}=await r.consume(t,async o=>{if(!o)return null;let c=Date.now(),l=()=>Date.now()-c,{[D]:m,[O]:g,[k]:_,[h.CONTEXTS_IDS_HEADER]:v}=o.properties.headers??{},y=e.parseMsg(o),b=await this.lockRedisIfNeeded(y,i),x=(0,h.newTrace)(h.traceTypes.RABBIT);if(g&&f)try{await(0,h.createOrSetRabbitTrace)(x,g,v)}catch(e){return this.#t.error(`rabbit: failed to setRabbitTrace`,{userId:g,e}),this.nack(r,t,i,{messageTtl:s},o,b)(o)}if(m&&x.context?.set(D,m),d&&await d(t,{userId:g,automationId:_}),!await this.shouldConsumeMessageByTimestamp(y))return await this.unlockRedisIfNeeded(b),this.ack(r,o)(o);let S=!1,C=async()=>{S||(S=!0,await this.ack(r,o,!0,b)(o))},w=async(e,n={})=>{if(S)return;this.#t.debug(`rabbit localNack`,{messageAcked:S,uniqueId:a,deliveryTag:o.fields.deliveryTag}),S=!0;let c=l();await this.nack(r,t,i,{messageTtl:s},o,b)(o,{...n,consumptionDuration:c})};this.activeCallbackCount++;let T=null;try{return p&&(T=(0,u.addAbortListener)(this.shutdownAbortController.signal,()=>{this.#t.info(`rabbit: shutdown signal received, calling localNack for in-flight message`,{uniqueId:a,deliveryTag:o.fields.deliveryTag});let e=w(o,{isAborted:!0});this.#e.add(e),e.finally(()=>{this.#e.delete(e)})})),await n(y,C,w,this.shutdownAbortController.signal)}catch{return await w(o)}finally{this.activeCallbackCount--,T&&T?.[Symbol.dispose]()}},W);c?(this.#t.info(`rabbit: adding tag ${c} to the array old`),this.oldConsumersTags.set(t,{channel:r,consumerTag:c})):this.#t.error(`rabbit: failed to consume from queue ${t} old`)})}async getNewChannelOld({name:e=R().toString(),onClose:t=null,options:n={},connection:r}){let i;try{i=await this.getConnectionOld(r)}catch(t){throw this.#t.error(`rabbit: error on get connection for new channel ${e} `,{e:t}),t}if(!i)throw Error(`rabbit: couldnt get connection for new channel ${e}`);let a=i?.createChannel({...n});(0,u.once)(a,`close`).then(n=>{this.#t.error(`rabbit: channel ${e} closed`),t?.(n)});try{return await(0,u.once)(a,`connect`),this.#t.debug(`rabbit: channel ${e} CONNECTED`),a}catch(t){throw this.#t.error(`rabbit: channel error ${e} error`,{err:t}),t}}async getConnectionOld(e){let{amqpConnection:t,creatingConnection:n,connectionCreatedEventName:r,connectionFailedEventName:i,blockReconnect:a}=e;if(a){this.#t.debug(`rabbit: block reconnect`);return}if(t!==null){if(this.options?.disableReconnect||t?.isConnected())return this.#t.debug(`rabbit: connection - is connected`),t;this.#t.debug(`rabbit: connection - reconnecting`)}if(n){let[e,t]=await Promise.race([r,i].map(e=>(0,u.once)(this.oldEm,e).then(([t])=>[e,t])));if(e===r)return t;throw t}e.creatingConnection=!0;let o=!1,s=()=>{let e=process.env.RABBITMQ_USERNAME||`guest`,t=process.env.RABBITMQ_PASSWORD||`guest`,n=this.options?.rabbitHost||process.env.RABBITMQ_SERVICE_HOST||`localhost`;return this.#t.debug(`rabbit: creating connection`,{host:n,userName:e,HEARTBEAT:`60`}),[`amqp://${e}:${t}@${n}?heartbeat=60`]},c=await(0,p.connect)(s(),{findServers:s});e.amqpConnection=c;let{promise:l,reject:d,resolve:f}=z();return c.on(`error`,e=>{this.#t.error(`rabbit: connection error`,{err:e}),o||(o=!0,d(e),this.oldEm.emit(i,e))}),c.on(`connectFailed`,e=>{this.oldConsumersTags.clear(),typeof e.url==`string`&&(e.url=this.maskURL(e.url)),this.#t.error(`rabbit: connection connectFailed`,{err:e}),o||(o=!0,d(e),this.oldEm.emit(i,e))}),c.on(`disconnect`,({err:t})=>{this.oldConsumersTags.clear(),this.#t.debug(`rabbit: connection closed`),this.options?.disableReconnect?(this.#t.error(`${this.DISCONNECT_MSG}${t&&` - ${t}`}`),e.blockReconnect=!0):this.#t.error(`${this.RECONNECT_MSG}${t&&` - ${t}`}`)}),c.once(`connect`,async()=>{this.#t.debug(`rabbit: connection established`),e.creatingConnection=!1,this.oldEm.emit(r,c),o=!0,f(c)}),l}saveConsumerOld(e,t,n){this.oldConsumersToRegister.some(t=>t.queue===e)||(this.#t.info(`rabbit: consumer: ${e} saved in consumer array`),this.oldConsumersToRegister.push({queue:e,callback:t,options:n}))}async assertQueueOld(t,n){return e.validateName(`queue`,t),this.oldQueues[t]?(delete this.oldQueueSetupPromises[t],this.oldQueues[t]):(this.oldQueueSetupPromises[t]??=this.setupQueueOld(t,n),this.oldQueueSetupPromises[t])}async setupQueueOld(t,n){let r,i=this.oldPublishConnection,a=e.shouldUseQuorum(t),o={...n,durable:!0,arguments:{...n?.arguments,"x-consumer-timeout":1e3*60*60*24,"x-queue-type":a?`quorum`:`classic`}};try{let e=await this.assertChannelOld({connection:i});this.#t.debug(`assertQueue->channel.addSetup`,{queueName:t}),await e.addSetup(e=>e.assertQueue(t,o)),this.#t.debug(`assertQueue->channel.assertQueue`,{queueName:t}),r=await e.assertQueue(t,o)}catch(e){if(this.#t.error(`rabbit: assertQueue error`,{queueName:t,options:n,error:e}),this.options?.dontRetryAssert)throw e;{this.#t.debug(`retrying assertQueue`,{queueName:t});let e=await this.assertChannelOld({force:!0,connection:i});await this.deleteQueueOld(t,i),this.#t.debug(`retrying assertQueue->channel.addSetup`,{queueName:t}),await e.addSetup(e=>e.assertQueue(t,o)),this.#t.debug(`retrying assertQueue->channel.assertQueue`,{queueName:t}),r=await e.assertQueue(t,o)}}return this.oldQueues[t]=r,r}async assertChannelOld({force:e=!1,connection:t}){if(this.oldPublishChannel&&!e)return this.oldPublishChannel;if(this.oldPublishChannelSetupPromise)return this.oldPublishChannelSetupPromise;let{promise:n,resolve:r,reject:i}=z();this.oldPublishChannelSetupPromise=n;try{let e=await this.getNewChannelOld({connection:t});e.on(`error`,e=>{this.#t.error(`rabbit: channel error`,{err:e})}),this.oldPublishConnection===t&&(this.oldPublishChannel=e),r(e)}catch(e){i(e)}return n}async deleteQueueOld(t,n){e.validateName(`queue`,t);let r=await this.assertChannelOld({connection:n});this.#t.info(`rabbit: deleting queue`,{queue:t});let i=await r.deleteQueue(t);return this.#t.debug(`queue deleted`,i),i}async assertExchangeOld(e,t){let n=await this.assertChannelOld({connection:t});return this.oldExchanges[e]?(delete this.oldAssertExchangePromises[e],this.oldExchanges[e]):this.oldAssertExchangePromises[e]?this.oldAssertExchangePromises[e]:(this.oldAssertExchangePromises[e]=L(n,e),this.oldExchanges[e]=await this.oldAssertExchangePromises[e],this.oldExchanges[e])}async isConnectedOld(){if(this.gracefulShutdownStarted)return!0;this.#t.debug(`rabbit: start is connected old`);let[e,t]=await Promise.all([this.getConnectionOld(this.oldConsumeConnection),this.getConnectionOld(this.oldPublishConnection)]);if(!(e.isConnected()&&t.isConnected()))return this.#t.error(`rabbit: isConnected old - false`),!1;try{let e=this.oldConsumersToRegister.filter(e=>!this.oldConsumersTags.get(e.queue));if(e.length>0){let t=e.map(e=>e.queue);throw this.#t.error(`rabbit: found unregistered consumers for queues old`,{count:t.length,queues:t}),new x(`Found unregistered consumers old`)}let t=await this.assertChannelOld({connection:this.oldPublishConnection});return await t.waitForConnect(),await Promise.all(this.oldConsumersToRegister.map(e=>t.checkQueue(e.queue))),!0}catch(e){return this.#t.error(`rabbit: isConnected - false old`,{msg:e.message}),!1}}},Q=Z;exports.default=Q,exports.sendCeleryTaskViaHttp=K,exports.t=s;
|
|
1
|
+
Object.defineProperty(exports,`__esModule`,{value:!0});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`node:process`),l=require(`node:timers/promises`),u=require(`node:events`),d=require(`moment`);d=s(d);let f=require(`redis-lock`);f=s(f);let p=require(`amqp-connection-manager`),m=require(`exponential-backoff`),h=require(`@autofleet/zehut`),g=require(`node:crypto`),_=require(`@autofleet/logger`);_=s(_);let v=require(`redis`);const y=(0,_.default)();var b=y,x=class extends Error{constructor(e){super(e),this.name=`RabbitError`}};const S=e=>(0,v.createClient)({socket:e});var C=S;const w=6e4*60*12,T=1e3*5,E=`x-retry-count`,D=`x-trace-id`,O=`x-af-user-id`,k=`x-af-automation-id`,A=`x-total-processing-duration-ms`,j=`x-redelivered-count`,M=!1,N={limit:1,retries:1,deadMessageTtl:432e5,lockTimeout:T,useConsumeWithLock:!1,auditContext:null,enableRabbitTrace:!1,callNackOnAbort:!1},P={startingDelay:500,timeMultiple:4,numOfAttempts:5},F={startingDelay:1,timeMultiple:1,numOfAttempts:5},I=`ha-promote-on-failure`,L=`ha-promote-on-shutdown`,R={arguments:{"ha-promote-on-failure":`always`,"ha-promote-on-shutdown":`always`}},z={CONSUMER:`consumer`,PUBLISHER:`publisher`},{PROJECT_ID:B}=process.env,V=async(e,t)=>e.assertExchange(t,`fanout`),H=()=>Math.floor(Math.random()*1e5),U=()=>{if(Promise.withResolvers)return Promise.withResolvers();let e,t;return{promise:new Promise((n,r)=>{e=n,t=r}),resolve:e,reject:t}},W=()=>[`af-experiment-manager`,`dev1-experiment-manager`].includes(B||``),G=()=>W()?P:F,K={host:process.env.RABBITMQ_SERVICE_HOST||`localhost`,username:process.env.RABBITMQ_USERNAME||`guest`,password:process.env.RABBITMQ_PASSWORD||`guest`};async function q(e,{taskName:t,queueName:n}){let r=`http://${K.host}:15672/api/exchanges/%2f/amq.default/publish`,i={task:t,id:(0,g.randomUUID)(),args:[e]},a={properties:{delivery_mode:2,content_type:`application/json`},routing_key:n,payload:JSON.stringify(i),payload_encoding:`string`};try{let e=await fetch(r,{method:`POST`,headers:{"Content-Type":`application/json`,Authorization:`Basic ${Buffer.from(`${K.username}:${K.password}`).toString(`base64`)}`},body:JSON.stringify(a)});if(e.ok){let t=await e.json();b.info(`Successfully published message:`,t)}else b.error(`Failed to publish message. Status code: ${e.status}`),b.error(`Response: ${await e.text()}`)}catch(e){throw b.error(`Error sending request:`,e instanceof Error?e.message:String(e)),e}}const J=1e3*10,{DISABLE_QUORUM_QUEUES_CONSUME:Y,DISABLE_QUORUM_QUEUES_PUBLISH:X,MY_POD_NAME:Z}=c.env,ee=`60`;var Q=class e{static parseMsg(e){let t=e.content.toString();try{t=JSON.parse(t)}catch{}return{...e,content:t}}static validateName(e,t){if(!t||t===``)throw new x(`error while using ${e} with no name`)}static getPublishOptions(e={}){let t=(0,h.getUser)(),n=h.outbreak.getCurrentContextTraceId();return{timestamp:(0,d.default)().unix(),timeout:1e4,headers:{creationTimestamp:(0,d.default)().valueOf(),...e,...t?.id===void 0?{}:{[O]:t.id},...t?.contextIds===void 0?{}:{[h.CONTEXTS_IDS_HEADER]:t.contextIds},...n===void 0?{}:{[D]:n}}}}#e=new Set;#t;constructor(t={},n){this.options=t,this.redisConfig=n,this.DISCONNECT_MSG=`rabbit: connection disconnect`,this.RECONNECT_MSG=`rabbit: connection disconnect - reconnecting`,this.publishChannel=null,this.publishChannelSetupPromise=null,this.publishConnection={amqpConnection:null,creatingConnection:!1,connectionCreatedEventName:`publishConnectionCreated`,connectionFailedEventName:`publishConnectionFailed`,blockReconnect:!1,connectionRole:z.PUBLISHER},this.consumeConnection={amqpConnection:null,creatingConnection:!1,connectionCreatedEventName:`consumeConnectionCreated`,connectionFailedEventName:`consumeConnectionFailed`,blockReconnect:!1,connectionRole:z.CONSUMER},this.em=new u.EventEmitter,this.exchanges={},this.queues={},this.queueSetupPromises={},this.assertExchangePromises={},this.consumersTags=new Map,this.consumersToRegister=[],this.pendingConsumers=[],this.doesVHostExist=!1,this.vhost=`quorum-vhost`,this.gracefulShutdownStarted=!1,this.activeCallbackCount=0,this.shutdownAbortController=new AbortController,this.oldPublishChannel=null,this.oldPublishChannelSetupPromise=null,this.oldPublishConnection={amqpConnection:null,creatingConnection:!1,connectionCreatedEventName:`oldPublishConnectionCreated`,connectionFailedEventName:`oldPublishConnectionFailed`,blockReconnect:!1,connectionRole:z.PUBLISHER},this.oldConsumeConnection={amqpConnection:null,creatingConnection:!1,connectionCreatedEventName:`oldConsumeConnectionCreated`,connectionFailedEventName:`oldConsumeConnectionFailed`,blockReconnect:!1,connectionRole:z.CONSUMER},this.oldEm=new u.EventEmitter,this.oldExchanges={},this.oldQueues={},this.oldQueueSetupPromises={},this.oldAssertExchangePromises={},this.oldConsumersTags=new Map,this.oldConsumersToRegister=[],this.assertVHost=async()=>{if(this.doesVHostExist)return;let e=process.env.RABBITMQ_USERNAME||`guest`,t=process.env.RABBITMQ_PASSWORD||`guest`,n={Authorization:`Basic ${Buffer.from(`${e}:${t}`).toString(`base64`)}`,"Content-Type":`application/json`},r=`${`http://${(this.options?.rabbitHost||process.env.RABBITMQ_SERVICE_HOST||`localhost`).split(`:`)[0]}:15672`}/api/vhosts/${encodeURIComponent(this.vhost)}`;try{let e=await fetch(r,{method:`GET`,headers:n});if(e.status===200){this.doesVHostExist=!0,this.#t.info(`Vhost exists`,{vhost:this.vhost});return}if(e.status!==404)throw this.#t.error(`Failed to check vhost`,{response:e}),new x(`Failed to check vhost`);let t=await fetch(r,{method:`PUT`,headers:n,body:JSON.stringify({default_queue_type:`quorum`})});if(!t.ok)throw this.#t.error(`Failed to create vhost`,{response:t}),new x(`Failed to create vhost`);this.doesVHostExist=!0,this.#t.info(`Vhost created`,{vhost:this.vhost})}catch(e){throw this.#t.error(`Failed to check or create vhost`,{error:e}),e}},this.shouldConsumeMessageByTimestamp=async e=>{if(!e)return!1;let{headers:t}=e.properties,n=t?.creationTimestamp;if(n&&t?.redisTimestampValidationKey&&this.redisClient){let e=this.getRedisKey(t.redisTimestampValidationKey),r=await this.redisClient.get(e);return!r||parseInt(r,10)<=parseInt(n,10)}return!0},this.ack=(e,t,n=!1,r=null)=>async i=>{if(!t)return;this.#t.debug(`rabbit acking message`,{deliveryTag:t.fields.deliveryTag}),await e.ack(t);let{headers:a}=t.properties,o=a?.creationTimestamp;if(n&&o&&a?.redisTimestampValidationKey&&this.redisClient){let e=parseInt(o,10),t=this.getRedisKey(a.redisTimestampValidationKey);await this.redisClient.set(t,e,{EX:3600}),await this.unlockRedisIfNeeded(r)}},this.nack=(t,n,r,i,a,o)=>async(s,{skipRetry:c=!1,consumptionDuration:l,isAborted:u=!1}={})=>{if(await this.unlockRedisIfNeeded(o),!t||!a){this.#t.error(`no channel or msg`,{msg:a});return}let d=this.getRetriesCount(a),f=this.getTotalConsumptionDuration(a)??0,p=this.getRedeliveredCount(a),m=f+(l??0),h=(c||d>=r.retries)&&!u;this.#t.info(`nack message`,{deliveryTag:a.fields.deliveryTag,currentRetryCount:d,updatedConsumptionDuration:m,sendToDeadQueue:h,isAborted:u}),await this.sendToQueue(`${n}${h?`-dead`:``}`,e.parseMsg(a).content,h?i:r,{...a.properties.headers,[E]:u?d:d+1,[A]:m,[j]:u?p+1:p},r.isQuorumQueue),this.#t.debug(`rabbit nacking message`,{deliveryTag:a.fields.deliveryTag}),await t.ack(a)},this.maskURL=e=>{try{let t=new URL(e);return t.username=`***`,t.password=`***`,t.toString()}catch{return e}},this.#t=t?.logger??b,this.identifier=t?.identifier||Z||(0,g.randomUUID)();let{redisClient:r,redisLock:i}=this.#n(t,n);this.redisClient=r,this.redisLock=i,this.options?.dontGracefulShutdown||(this.#t.info(`rabbit: [gracefully-shutdown] adding gracefully shutdown for process.pid ${process.pid}`),process.on(`SIGTERM`,async()=>{await this.gracefulShutdown(`SIGTERM`)}),process.on(`SIGINT`,async()=>{await this.gracefulShutdown(`SIGINT`)}))}#n(e,t){let n;return!t&&!e?.redisClient?(this.#t.info(`rabbit: No Redis config provided and no Redis client provided, disabling Redis features`),{redisClient:void 0,redisLock:void 0}):(e?.redisClient?(this.#t.info(`rabbit: Using provided redis client`),n=e.redisClient):(this.#t.info(`rabbit: No redis client provided, creating new redis client`),n=C(t).on(`error`,e=>{this.#t.error(`rabbit: Redis error`,{err:e,redisConfig:t})}),n.connect().catch(e=>{this.#t.error(`rabbit: Failed to connect to Redis`,{err:e,redisConfig:t})})),{redisClient:n,redisLock:(0,f.default)(n)})}getRedisKey(e){return`${this.redisConfig?.prefix||``}${e}`}getRetriesCount(e){let t=e.properties.headers?.[E];return Number.parseInt(t||`0`,10)||0}getRedeliveredCount(e){let t=e.properties.headers?.[j];return Number.parseInt(t||`0`,10)||0}getTotalConsumptionDuration(e){let t=e.properties.headers?.[A];return t?Number.parseInt(t,10):0}async getConnection(e){let{amqpConnection:t,creatingConnection:n,connectionCreatedEventName:r,connectionFailedEventName:i,blockReconnect:a,connectionRole:o}=e;if(a){this.#t.debug(`rabbit: block reconnect`);return}if(t!==null){if(this.options?.disableReconnect||t?.isConnected())return this.#t.debug(`rabbit: connection - is connected`),t;this.#t.debug(`rabbit: connection - reconnecting`)}if(n){this.#t.debug(`rabbit: creating connection emi`);let[e,t]=await Promise.race([r,i].map(e=>(0,u.once)(this.em,e).then(([t])=>[e,t])));if(e===r)return t;throw t}e.creatingConnection=!0;let s=!1,c=()=>{let e=process.env.RABBITMQ_USERNAME||`guest`,t=process.env.RABBITMQ_PASSWORD||`guest`,n=this.options?.rabbitHost||process.env.RABBITMQ_SERVICE_HOST||`localhost`;return this.#t.debug(`rabbit: creating connection`,{host:n,userName:e,HEARTBEAT:`60`}),[`amqp://${e}:${t}@${n}/${this.vhost}?heartbeat=60`]},l=(0,p.connect)(c(),{findServers:c,connectionOptions:{clientProperties:{connection_name:`${this.identifier}:quorum:${o}`}}});e.amqpConnection=l;let{promise:d,reject:f,resolve:m}=U();return l.on(`error`,e=>{this.#t.error(`rabbit: connection error`,{err:e}),s||(s=!0,f(e),this.em.emit(i,e))}),l.on(`connectFailed`,e=>{this.consumersTags.clear(),typeof e.url==`string`&&(e.url=this.maskURL(e.url)),this.#t.error(`rabbit: connection connectFailed`,{err:e,advice:`Check if the vhost exist`,vhost:this.vhost}),s||(s=!0,f(e),this.em.emit(i,e))}),l.on(`disconnect`,({err:t})=>{this.consumersTags.clear(),this.#t.debug(`rabbit: connection closed`),this.options?.disableReconnect?(this.#t.error(`${this.DISCONNECT_MSG}${t&&` - ${t}`}`),e.blockReconnect=!0):this.#t.error(`${this.RECONNECT_MSG}${t&&` - ${t}`}`)}),l.once(`connect`,async()=>{this.#t.debug(`rabbit: connection established`),e.creatingConnection=!1,this.em.emit(r,l),s=!0,m(l)}),d}async getNewChannel({name:e=H().toString(),onClose:t=null,options:n={},connection:r}){let i;try{i=await this.getConnection(r)}catch(t){throw this.#t.error(`rabbit: error on get connection for new channel ${e} `,{e:t}),t}let a=i?.createChannel({...n,name:e});(0,u.once)(a,`close`).then(n=>{this.#t.error(`rabbit: channel ${e} closed`),t?.(n)});try{return await(0,u.once)(a,`connect`),this.#t.debug(`rabbit: channel ${e} CONNECTED`),a}catch(t){throw this.#t.error(`rabbit: channel error ${e} error`,{err:t}),t}}async assertChannel({force:e=!1,connection:t}){if(this.publishChannel&&!e)return this.publishChannel;if(this.publishChannelSetupPromise)return this.publishChannelSetupPromise;let{promise:n,resolve:r,reject:i}=U();this.publishChannelSetupPromise=n;try{let e=await this.getNewChannel({connection:t,...t.connectionRole===z.PUBLISHER&&{name:`publish:${this.identifier}:quorum`}});e.on(`error`,e=>{this.#t.error(`rabbit: channel error`,{err:e})}),this.publishConnection===t&&(this.publishChannel=e),r(e)}catch(e){i(e)}return n}async assertExchange(e,t){let n=await this.assertChannel({connection:t});return this.exchanges[e]?(delete this.assertExchangePromises[e],this.exchanges[e]):this.assertExchangePromises[e]?this.assertExchangePromises[e]:(this.assertExchangePromises[e]=V(n,e),this.exchanges[e]=await this.assertExchangePromises[e],this.exchanges[e])}async getQueueLength(t){e.validateName(`queue`,t);let{publishChannel:n}=this;if(!n)throw new x(`channel is not defined`);return this.#t.debug(`rabbit: getting queue length`,{queue:t,connected:this.publishConnection.amqpConnection?.isConnected()}),n?.checkQueue(t)}async deleteQueue(t,n){e.validateName(`queue`,t);let r=await this.assertChannel({connection:n});this.#t.info(`rabbit: deleting queue`,{queue:t});let i=await r.deleteQueue(t);return this.#t.debug(`queue deleted`,i),i}async bindQueue(e,t){let n=await this.assertChannel({connection:this.publishConnection});return await n.addSetup(n=>n.bindQueue(e,t,``)),n.bindQueue(e,t,``)}async setupQueue(e,t){let n,r=this.publishConnection,i={...t,durable:!0,arguments:{...t?.arguments,"x-consumer-timeout":1e3*60*60*24,"x-queue-type":`quorum`}};try{let t=await this.assertChannel({connection:r});this.#t.debug(`assertQueue->channel.addSetup`,{queueName:e}),await t.addSetup(async t=>{await t.assertQueue(e,i)}),this.#t.debug(`assertQueue->channel.assertQueue`,{queueName:e}),n=await t.assertQueue(e,i)}catch(a){if(this.#t.error(`rabbit: assertQueue error`,{queueName:e,options:t,error:a}),this.options?.dontRetryAssert)throw a;{this.#t.debug(`retrying assertQueue`,{queueName:e});let t=await this.assertChannel({force:!0,connection:r});await this.deleteQueue(e,r),this.#t.debug(`retrying assertQueue->channel.addSetup`,{queueName:e}),await t.addSetup(t=>t.assertQueue(e,i)),this.#t.debug(`retrying assertQueue->channel.assertQueue`,{queueName:e}),n=await t.assertQueue(e,i)}}return this.queues[e]=n,n}static shouldUseQuorum(e){let t=process.env.QUORUM_QUEUES_WHITELIST;return t===`*`?!0:t?t.split(`,`).includes(e):!1}async assertQueue(t,n){return e.validateName(`queue`,t),this.queues[t]?(delete this.queueSetupPromises[t],this.queues[t]):(this.queueSetupPromises[t]??=this.setupQueue(t,n),this.queueSetupPromises[t])}saveConsumer(e,t,n){this.consumersToRegister.some(t=>t.queue===e)||(this.#t.info(`rabbit: consumer: ${e} saved in consumer array`),this.consumersToRegister.push({queue:e,callback:t,options:n}))}async consume(e,t,n){let r=async()=>{this.saveConsumerOld(e,t,n),n?.isQuorumQueue!==!1&&Y!==`true`&&(this.saveConsumer(e,t,n),await(0,m.backOff)(()=>this.assertVHost(),G()),await this.consumeNew(e,t,n)),await this.consumeOld(e,t,n)};this.options.autoStartConsumers===!1?this.pendingConsumers.push(r):await r()}async consumeNew(e,t,n){await this.consumeFromRabbit(e,t,n)}async consumeOld(e,t,n){await this.consumeFromRabbitOld(e,t,n)}async lockRedisIfNeeded(e,t){let{properties:{headers:n}}=e,r=n?.creationTimestamp,i=null;return t.useConsumeWithLock&&r&&n?.redisTimestampValidationKey&&this.redisLock&&(i=await this.redisLock(n.redisTimestampValidationKey,t?.lockTimeout||T)),i}async unlockRedisIfNeeded(e){this.redisLock&&e&&await e()}async consumeFromRabbit(t,n,r){let i={...N,...r};e.validateName(`queue`,t);let a=(0,g.randomUUID)(),{limit:o,deadMessageTtl:s,useConsumeWithLock:c,lockTimeout:l,auditContext:d,enableRabbitTrace:f}=i;if(c){if(!this.redisLock)throw new x(`Usage of consumeWithLock requires RedisInstance`);this.#t.info(`rabbit: Consuming with lock from queue ${t} with lockTimeout: ${l}ms`)}return(await this.getNewChannel({connection:this.consumeConnection,name:`consume:${this.identifier}:${t}:quorum`})).addSetup(async c=>{await this.assertQueue(t,i),await c.prefetch(o,!1);let{consumerTag:l}=await c.consume(t,async o=>{if(!o)return null;let l=Date.now(),p=()=>Date.now()-l,{[D]:m,[O]:g,[k]:_,[h.CONTEXTS_IDS_HEADER]:v}=o.properties.headers??{},y=e.parseMsg(o),b=await this.lockRedisIfNeeded(y,i),x=(0,h.newTrace)(h.traceTypes.RABBIT);if(g&&f)try{await(0,h.createOrSetRabbitTrace)(x,g,v)}catch(e){return this.#t.error(`rabbit: failed to setRabbitTrace`,{userId:g,e}),this.nack(c,t,i,{messageTtl:s},o,b)(o)}if(m&&x.context?.set(D,m),d&&await d(t,{userId:g,automationId:_}),!await this.shouldConsumeMessageByTimestamp(y))return await this.unlockRedisIfNeeded(b),this.ack(c,o)(o);let S=!1,C=async()=>{S||(S=!0,await this.ack(c,o,!0,b)(o))},w=async(e,n={})=>{if(S)return;this.#t.debug(`rabbit localNack`,{messageAcked:S,uniqueId:a,deliveryTag:o.fields.deliveryTag}),S=!0;let r=p();await this.nack(c,t,i,{messageTtl:s},o,b)(o,{...n,consumptionDuration:r})};this.activeCallbackCount++;let T=null;try{return r?.callNackOnAbort&&(T=(0,u.addAbortListener)(this.shutdownAbortController.signal,()=>{this.#t.warn(`rabbit: callback aborted`,{uniqueId:a,deliveryTag:o.fields.deliveryTag});let e=w(o,{isAborted:!0});this.#e.add(e),e.finally(()=>{this.#e.delete(e)})})),await n(y,C,w,this.shutdownAbortController.signal)}catch{return await w(o)}finally{this.activeCallbackCount--,T&&T[Symbol.dispose]()}},{...R,consumerTag:`${this.identifier}:${t}:quorum`});l?(this.#t.info(`rabbit: adding tag ${l} to the array.`),this.consumersTags.set(t,{channel:c,consumerTag:l})):this.#t.error(`rabbit: failed to consume from queue ${t}`)})}async consumeFromExchange(t,n,r,i){let a={...N,...i};e.validateName(`exchange`,n),e.validateName(`queue`,t);let{limit:o}=a,s=async()=>{i?.isQuorumQueue!==!1&&Y!==`true`&&(await this.saveConsumer(t,r,i),await(0,m.backOff)(()=>this.assertVHost(),G()),await(await this.getNewChannel({name:`consume-from-exchange:${this.identifier}:${n}:${t}:quorum`,connection:this.consumeConnection})).addSetup(async e=>{let a=await V(e,n);return await e.assertQueue(t),this.exchanges[n]=a,await e.prefetch(o,!1),Promise.all([e.bindQueue(t,n,``),this.consumeNew(t,r,i)])})),await this.saveConsumerOld(t,r,i),await(await this.getNewChannelOld({name:`consume-from-exchange:${this.identifier}:${n}:${t}`,connection:this.oldConsumeConnection})).addSetup(async e=>{let a=await V(e,n);return await e.assertQueue(t),this.oldExchanges[n]=a,await e.prefetch(o,!1),Promise.all([e.bindQueue(t,n,``),this.consumeOld(t,r,i)])})};this.options.autoStartConsumers===!1?this.pendingConsumers.push(s):await s()}async startConsume(){await Promise.all(this.pendingConsumers.map(e=>e())),this.pendingConsumers.length=0}async publish(t,n,r,i=!0){if(await(0,l.setImmediate)(),i&&X!==`true`){await this.assertVHost(),e.validateName(`exchange`,t);let i=await this.assertChannel({connection:this.publishConnection});await this.assertExchange(t,this.publishConnection),await i.publish(t,``,Buffer.from(JSON.stringify(n)),e.getPublishOptions(r));return}e.validateName(`exchange`,t);let a=await this.assertChannelOld({connection:this.oldPublishConnection});await this.assertExchangeOld(t,this.oldPublishConnection),await a.publish(t,``,Buffer.from(JSON.stringify(n)),e.getPublishOptions(r))}async sendToQueue(t,n,r,i,a=!0){if(a&&X!==`true`){try{await this.assertVHost(),await this.assertChannel({connection:this.publishConnection})}catch(e){throw this.#t.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${t}`,{e}),e}try{e.validateName(`queue`,t),await this.assertQueue(t,r)}catch(e){throw this.#t.error(`rabbit sendToQueue: failed to assert queue ${t}`,{e}),e}try{let r=await this.publishChannel?.sendToQueue(t,Buffer.from(JSON.stringify(n)),e.getPublishOptions(i));return this.#t.debug(`rabbit: sending to queue ${t}`,{res:r}),r}catch(e){let n=await this.isConnected();throw this.#t.error(`rabbit sendToQueue: failed to send to queue ${t}, isConnected: ${n}`,{e}),e}}else{try{await this.assertChannelOld({connection:this.oldPublishConnection})}catch(e){throw this.#t.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${t}`,{e}),e}try{e.validateName(`queue`,t),await this.assertQueueOld(t,r)}catch(e){throw this.#t.error(`rabbit sendToQueue: failed to assert queue ${t}`,{e}),e}try{let r=await this.oldPublishChannel?.sendToQueue(t,Buffer.from(JSON.stringify(n)),e.getPublishOptions(i));return this.#t.debug(`rabbit: sending to queue ${t}`,{res:r}),r}catch(e){let n=await this.isConnectedOld();throw this.#t.error(`rabbit sendToQueue: failed to send to queue ${t}, isConnected: ${n}`,{e}),e}}}async isConnected(){let e=!0;if(!this.gracefulShutdownStarted){if(Y!==`true`||X!==`true`){this.#t.debug(`rabbit: start is connected`);let[t,n]=await Promise.all([this.getConnection(this.consumeConnection),this.getConnection(this.publishConnection)]);if(e=t.isConnected()&&n.isConnected(),!e)return this.#t.error(`rabbit: isConnected - false`),!1;try{let e=this.consumersToRegister.filter(e=>!this.consumersTags.get(e.queue));if(e.length>0){let t=e.map(e=>e.queue);throw this.#t.error(`rabbit: found unregistered consumers for queues`,{count:t.length,queues:t}),new x(`Found unregistered consumers`)}let t=await this.assertChannel({connection:this.publishConnection});await t.waitForConnect(),await Promise.all(this.consumersToRegister.map(e=>t.checkQueue(e.queue)))}catch(e){return this.#t.error(`rabbit: isConnected - false`,{msg:e.message}),!1}}e=await this.isConnectedOld()}return this.#t.debug(`rabbit: isConnected - ${e}`),e}async stopAcceptingWork(){this.#t.info(`rabbit: stop accepting work - canceling all tags`);let e=[...this.consumersTags].map(([,{channel:e,consumerTag:t}])=>e.cancel(t)),t=[...this.oldConsumersTags].map(([,{channel:e,consumerTag:t}])=>e.cancel(t));this.consumersTags.clear(),this.oldConsumersTags.clear();let n=(await Promise.allSettled([...e,...t])).filter(e=>e.status===`rejected`);n.length>0?this.#t.warn(`rabbit: stop accepting work - failed to cancel #${n.length} tags: ${n}`):this.#t.info(`rabbit: stop accepting work - all tags successfully canceled.`)}async drain(e=1e4,t=!0){let n=Date.now(),r=this.activeCallbackCount;if(this.#t.info(`rabbit: [drain] waiting for active callbacks`,{activeCallbackCount:r,timeoutMs:e}),r===0){this.#t.info(`rabbit: [drain] no active callbacks to drain`);return}let i=this.publishChannel?.queueLength()??0;for(i>0&&this.#t.warn(`rabbit: [drain] there are still published messages waiting in the channel buffer`,{publishedMessagesWaiting:i});this.activeCallbackCount>0;){let r=Date.now()-n;if(r>=e){let n=this.activeCallbackCount;if(this.#t.warn(`rabbit: [drain] timeout waiting for callbacks`,{duration:r,remainingCallbacks:n,timeoutMs:e}),this.shutdownAbortController.abort(Error(`Graceful shutdown initiated by drain timeout after ${r}ms with ${n} callbacks still active`)),await Promise.allSettled([...this.#e]),t)throw Error(`Drain timeout after ${e}ms - ${n} callbacks still active`);return}await new Promise(e=>setTimeout(e,50))}let a=Date.now()-n;this.#t.info(`rabbit: [drain] all callbacks completed`,{duration:a,callbackCount:r})}async closeConnections(){if(this.activeCallbackCount>0){this.#t.warn(`rabbit: closing connections received with active callbacks. not performing the close`,{activeCallbackCount:this.activeCallbackCount});return}this.#t.info(`rabbit: closing connections`);let e=[this.consumeConnection,this.publishConnection,this.oldConsumeConnection,this.oldPublishConnection];await Promise.allSettled(e.map(async e=>{if(e.amqpConnection)try{await e.amqpConnection.close(),this.#t.info(`rabbit: connection closed successfully`)}catch(e){this.#t.error(`rabbit: error closing connection`,{e})}}))}async gracefulShutdown(e){this.gracefulShutdownStarted=!0;let t=this.consumersTags.size+this.oldConsumersTags.size;this.#t.info(`rabbit: [gracefully-shutdown] received ${e}! canceling #${t} tags...`),await this.stopAcceptingWork(),this.shutdownAbortController.abort(Error(`Graceful shutdown initiated by signal: ${e}`))}async consumeFromRabbitOld(t,n,r){let i={...N,...r};e.validateName(`queue`,t);let a=(0,g.randomUUID)(),{limit:o,deadMessageTtl:s,useConsumeWithLock:c,lockTimeout:l,auditContext:d,enableRabbitTrace:f,callNackOnAbort:p}=i;if(c){if(!this.redisLock)throw new x(`Usage of consumeWithLock requires RedisInstance`);this.#t.info(`rabbit: Consuming with lock from queue ${t} with lockTimeout: ${l}ms`)}return(await this.getNewChannelOld({connection:this.oldConsumeConnection,name:`consume:${this.identifier}:${t}`})).addSetup(async r=>{await this.assertQueueOld(t,i),await r.prefetch(o,!1);let{consumerTag:c}=await r.consume(t,async o=>{if(!o)return null;let c=Date.now(),l=()=>Date.now()-c,{[D]:m,[O]:g,[k]:_,[h.CONTEXTS_IDS_HEADER]:v}=o.properties.headers??{},y=e.parseMsg(o),b=await this.lockRedisIfNeeded(y,i),x=(0,h.newTrace)(h.traceTypes.RABBIT);if(g&&f)try{await(0,h.createOrSetRabbitTrace)(x,g,v)}catch(e){return this.#t.error(`rabbit: failed to setRabbitTrace`,{userId:g,e}),this.nack(r,t,i,{messageTtl:s},o,b)(o)}if(m&&x.context?.set(D,m),d&&await d(t,{userId:g,automationId:_}),!await this.shouldConsumeMessageByTimestamp(y))return await this.unlockRedisIfNeeded(b),this.ack(r,o)(o);let S=!1,C=async()=>{S||(S=!0,await this.ack(r,o,!0,b)(o))},w=async(e,n={})=>{if(S)return;this.#t.debug(`rabbit localNack`,{messageAcked:S,uniqueId:a,deliveryTag:o.fields.deliveryTag}),S=!0;let c=l();await this.nack(r,t,i,{messageTtl:s},o,b)(o,{...n,consumptionDuration:c})};this.activeCallbackCount++;let T=null;try{return p&&(T=(0,u.addAbortListener)(this.shutdownAbortController.signal,()=>{this.#t.info(`rabbit: shutdown signal received, calling localNack for in-flight message`,{uniqueId:a,deliveryTag:o.fields.deliveryTag});let e=w(o,{isAborted:!0});this.#e.add(e),e.finally(()=>{this.#e.delete(e)})})),await n(y,C,w,this.shutdownAbortController.signal)}catch{return await w(o)}finally{this.activeCallbackCount--,T&&T?.[Symbol.dispose]()}},{...R,consumerTag:`${this.identifier}:${t}`});c?(this.#t.info(`rabbit: adding tag ${c} to the array old`),this.oldConsumersTags.set(t,{channel:r,consumerTag:c})):this.#t.error(`rabbit: failed to consume from queue ${t} old`)})}async getNewChannelOld({name:e=H().toString(),onClose:t=null,options:n={},connection:r}){let i;try{i=await this.getConnectionOld(r)}catch(t){throw this.#t.error(`rabbit: error on get connection for new channel ${e} `,{e:t}),t}if(!i)throw Error(`rabbit: couldnt get connection for new channel ${e}`);let a=i?.createChannel({...n,name:e});(0,u.once)(a,`close`).then(n=>{this.#t.error(`rabbit: channel ${e} closed`),t?.(n)});try{return await(0,u.once)(a,`connect`),this.#t.debug(`rabbit: channel ${e} CONNECTED`),a}catch(t){throw this.#t.error(`rabbit: channel error ${e} error`,{err:t}),t}}async getConnectionOld(e){let{amqpConnection:t,creatingConnection:n,connectionCreatedEventName:r,connectionFailedEventName:i,blockReconnect:a,connectionRole:o}=e;if(a){this.#t.debug(`rabbit: block reconnect`);return}if(t!==null){if(this.options?.disableReconnect||t?.isConnected())return this.#t.debug(`rabbit: connection - is connected`),t;this.#t.debug(`rabbit: connection - reconnecting`)}if(n){let[e,t]=await Promise.race([r,i].map(e=>(0,u.once)(this.oldEm,e).then(([t])=>[e,t])));if(e===r)return t;throw t}e.creatingConnection=!0;let s=!1,c=()=>{let e=process.env.RABBITMQ_USERNAME||`guest`,t=process.env.RABBITMQ_PASSWORD||`guest`,n=this.options?.rabbitHost||process.env.RABBITMQ_SERVICE_HOST||`localhost`;return this.#t.debug(`rabbit: creating connection`,{host:n,userName:e,HEARTBEAT:`60`}),[`amqp://${e}:${t}@${n}?heartbeat=60`]},l=await(0,p.connect)(c(),{findServers:c,connectionOptions:{clientProperties:{connection_name:`${this.identifier}:${o}`}}});e.amqpConnection=l;let{promise:d,reject:f,resolve:m}=U();return l.on(`error`,e=>{this.#t.error(`rabbit: connection error`,{err:e}),s||(s=!0,f(e),this.oldEm.emit(i,e))}),l.on(`connectFailed`,e=>{this.oldConsumersTags.clear(),typeof e.url==`string`&&(e.url=this.maskURL(e.url)),this.#t.error(`rabbit: connection connectFailed`,{err:e}),s||(s=!0,f(e),this.oldEm.emit(i,e))}),l.on(`disconnect`,({err:t})=>{this.oldConsumersTags.clear(),this.#t.debug(`rabbit: connection closed`),this.options?.disableReconnect?(this.#t.error(`${this.DISCONNECT_MSG}${t&&` - ${t}`}`),e.blockReconnect=!0):this.#t.error(`${this.RECONNECT_MSG}${t&&` - ${t}`}`)}),l.once(`connect`,async()=>{this.#t.debug(`rabbit: connection established`),e.creatingConnection=!1,this.oldEm.emit(r,l),s=!0,m(l)}),d}saveConsumerOld(e,t,n){this.oldConsumersToRegister.some(t=>t.queue===e)||(this.#t.info(`rabbit: consumer: ${e} saved in consumer array`),this.oldConsumersToRegister.push({queue:e,callback:t,options:n}))}async assertQueueOld(t,n){return e.validateName(`queue`,t),this.oldQueues[t]?(delete this.oldQueueSetupPromises[t],this.oldQueues[t]):(this.oldQueueSetupPromises[t]??=this.setupQueueOld(t,n),this.oldQueueSetupPromises[t])}async setupQueueOld(t,n){let r,i=this.oldPublishConnection,a=e.shouldUseQuorum(t),o={...n,durable:!0,arguments:{...n?.arguments,"x-consumer-timeout":1e3*60*60*24,"x-queue-type":a?`quorum`:`classic`}};try{let e=await this.assertChannelOld({connection:i});this.#t.debug(`assertQueue->channel.addSetup`,{queueName:t}),await e.addSetup(e=>e.assertQueue(t,o)),this.#t.debug(`assertQueue->channel.assertQueue`,{queueName:t}),r=await e.assertQueue(t,o)}catch(e){if(this.#t.error(`rabbit: assertQueue error`,{queueName:t,options:n,error:e}),this.options?.dontRetryAssert)throw e;{this.#t.debug(`retrying assertQueue`,{queueName:t});let e=await this.assertChannelOld({force:!0,connection:i});await this.deleteQueueOld(t,i),this.#t.debug(`retrying assertQueue->channel.addSetup`,{queueName:t}),await e.addSetup(e=>e.assertQueue(t,o)),this.#t.debug(`retrying assertQueue->channel.assertQueue`,{queueName:t}),r=await e.assertQueue(t,o)}}return this.oldQueues[t]=r,r}async assertChannelOld({force:e=!1,connection:t}){if(this.oldPublishChannel&&!e)return this.oldPublishChannel;if(this.oldPublishChannelSetupPromise)return this.oldPublishChannelSetupPromise;let{promise:n,resolve:r,reject:i}=U();this.oldPublishChannelSetupPromise=n;try{let e=await this.getNewChannelOld({connection:t,...t.connectionRole===z.PUBLISHER&&{name:`publish:${this.identifier}`}});e.on(`error`,e=>{this.#t.error(`rabbit: channel error`,{err:e})}),this.oldPublishConnection===t&&(this.oldPublishChannel=e),r(e)}catch(e){i(e)}return n}async deleteQueueOld(t,n){e.validateName(`queue`,t);let r=await this.assertChannelOld({connection:n});this.#t.info(`rabbit: deleting queue`,{queue:t});let i=await r.deleteQueue(t);return this.#t.debug(`queue deleted`,i),i}async assertExchangeOld(e,t){let n=await this.assertChannelOld({connection:t});return this.oldExchanges[e]?(delete this.oldAssertExchangePromises[e],this.oldExchanges[e]):this.oldAssertExchangePromises[e]?this.oldAssertExchangePromises[e]:(this.oldAssertExchangePromises[e]=V(n,e),this.oldExchanges[e]=await this.oldAssertExchangePromises[e],this.oldExchanges[e])}async isConnectedOld(){if(this.gracefulShutdownStarted)return!0;this.#t.debug(`rabbit: start is connected old`);let[e,t]=await Promise.all([this.getConnectionOld(this.oldConsumeConnection),this.getConnectionOld(this.oldPublishConnection)]);if(!(e.isConnected()&&t.isConnected()))return this.#t.error(`rabbit: isConnected old - false`),!1;try{let e=this.oldConsumersToRegister.filter(e=>!this.oldConsumersTags.get(e.queue));if(e.length>0){let t=e.map(e=>e.queue);throw this.#t.error(`rabbit: found unregistered consumers for queues old`,{count:t.length,queues:t}),new x(`Found unregistered consumers old`)}let t=await this.assertChannelOld({connection:this.oldPublishConnection});return await t.waitForConnect(),await Promise.all(this.oldConsumersToRegister.map(e=>t.checkQueue(e.queue))),!0}catch(e){return this.#t.error(`rabbit: isConnected - false old`,{msg:e.message}),!1}}},$=Q;exports.default=$,exports.sendCeleryTaskViaHttp=q,exports.t=s;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|