@autofleet/rabbit 5.1.0-alpha.0 → 5.1.0-alpha.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 +3 -469
- package/dist/{index-CBlhQYQ_.d.cts → index-CioFCCR4.d.ts} +77 -27
- package/dist/{index-CdC0BR3j.d.ts → index-HKpuFnj9.d.cts} +77 -27
- 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.cjs +1 -1
- package/dist/mock/vitest.cjs.map +1 -1
- package/dist/mock/vitest.d.cts +2 -1
- package/dist/mock/vitest.d.ts +2 -1
- package/dist/mock/vitest.js +1 -1
- package/dist/mock/vitest.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
1
2
|
import RedisLock from "redis-lock";
|
|
2
3
|
import { AmqpConnectionManager, ChannelWrapper, CreateChannelOpts } from "amqp-connection-manager";
|
|
3
|
-
import { LoggerInstanceManager } from "@autofleet/logger";
|
|
4
|
-
import { createClient } from "redis";
|
|
5
4
|
import { PublishOptions } from "amqp-connection-manager/dist/types/ChannelWrapper";
|
|
6
5
|
import { ConfirmChannel, ConsumeMessage, Options, Replies } from "amqplib";
|
|
6
|
+
import { LoggerInstanceManager } from "@autofleet/logger";
|
|
7
|
+
import { createClient } from "redis";
|
|
7
8
|
|
|
8
9
|
//#region src/lib/redis.d.ts
|
|
9
10
|
interface RedisConfig {
|
|
@@ -14,6 +15,10 @@ interface RedisConfig {
|
|
|
14
15
|
declare const getRedisInstance: (config: RedisConfig) => ReturnType<typeof createClient>;
|
|
15
16
|
//#endregion
|
|
16
17
|
//#region src/lib/types.d.ts
|
|
18
|
+
type ExchangesCache = Record<string, Replies.AssertExchange | undefined>;
|
|
19
|
+
type QueuesCache = Record<string, Replies.AssertQueue | undefined>;
|
|
20
|
+
type QueueSetupPromisesDictionary = Record<string, Promise<Replies.AssertQueue> | undefined>;
|
|
21
|
+
type AssertExchangePromisesDictionary = Record<string, Promise<Replies.AssertExchange> | undefined>;
|
|
17
22
|
interface CustomMessageHeaders {
|
|
18
23
|
redisTimestampValidationKey?: string;
|
|
19
24
|
}
|
|
@@ -27,6 +32,7 @@ interface ConsumeOptions {
|
|
|
27
32
|
useConsumeWithLock?: boolean;
|
|
28
33
|
auditContext?: any;
|
|
29
34
|
enableRabbitTrace?: boolean;
|
|
35
|
+
isQuorumQueue?: boolean;
|
|
30
36
|
}
|
|
31
37
|
interface NackOptions {
|
|
32
38
|
skipRetry?: boolean;
|
|
@@ -34,7 +40,7 @@ interface NackOptions {
|
|
|
34
40
|
type Message = Omit<ConsumeMessage, "content"> & {
|
|
35
41
|
content: any;
|
|
36
42
|
};
|
|
37
|
-
type CallbackFunction = (msg: Message, ack: () => Promise<void>, nack: (ignored: ConsumeMessageOrNull, nackOptions?: NackOptions) => Promise<void
|
|
43
|
+
type CallbackFunction = (msg: Message, ack: () => Promise<void>, nack: (ignored: ConsumeMessageOrNull, nackOptions?: NackOptions) => Promise<void>, signal?: AbortSignal) => Promise<void>;
|
|
38
44
|
interface ConnectionData {
|
|
39
45
|
amqpConnection: AmqpConnectionManager | null;
|
|
40
46
|
creatingConnection: boolean;
|
|
@@ -58,7 +64,8 @@ declare function sendCeleryTaskViaHttp(data: TaskData, {
|
|
|
58
64
|
interface IAfRabbitMq {
|
|
59
65
|
ack(channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp?: boolean, releaseLock?: (() => Promise<void>) | null): (userMsg: ConsumeMessage) => Promise<void>;
|
|
60
66
|
nack(channel: ConfirmChannel, queue: string, options: ConsumeOptions, deadQueueOptions: Options.AssertQueue, msg: ConsumeMessageOrNull, releaseLock?: () => Promise<void>): (userMsg: ConsumeMessageOrNull, nackOptions?: NackOptions) => Promise<void>;
|
|
61
|
-
|
|
67
|
+
assertChannel(options?: assertChannelOpts): Promise<ChannelWrapper>;
|
|
68
|
+
assertExchange(exchangeName: string, connection: ConnectionData): Promise<Replies.AssertExchange>;
|
|
62
69
|
assertQueue(queueName: string, options?: Options.AssertQueue | null): Promise<Replies.AssertQueue>;
|
|
63
70
|
consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<void>;
|
|
64
71
|
consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<void>;
|
|
@@ -88,6 +95,11 @@ interface newChannelOpts {
|
|
|
88
95
|
options?: CreateChannelOpts | undefined;
|
|
89
96
|
connection: ConnectionData;
|
|
90
97
|
}
|
|
98
|
+
interface assertChannelOpts {
|
|
99
|
+
channelName?: string;
|
|
100
|
+
force?: boolean;
|
|
101
|
+
connection: ConnectionData;
|
|
102
|
+
}
|
|
91
103
|
declare class RabbitMq implements IAfRabbitMq {
|
|
92
104
|
#private;
|
|
93
105
|
readonly options: AfRabbitOptions;
|
|
@@ -97,61 +109,99 @@ declare class RabbitMq implements IAfRabbitMq {
|
|
|
97
109
|
static getPublishOptions(customHeaders?: CustomMessageHeaders): PublishOptions;
|
|
98
110
|
readonly DISCONNECT_MSG = "rabbit: connection disconnect";
|
|
99
111
|
readonly RECONNECT_MSG = "rabbit: connection disconnect - reconnecting";
|
|
112
|
+
publishChannel: ChannelWrapper | null;
|
|
113
|
+
publishChannelSetupPromise: Promise<ChannelWrapper> | null;
|
|
114
|
+
readonly publishConnection: ConnectionData;
|
|
115
|
+
readonly consumeConnection: ConnectionData;
|
|
116
|
+
readonly em: EventEmitter;
|
|
117
|
+
readonly exchanges: ExchangesCache;
|
|
118
|
+
readonly queues: QueuesCache;
|
|
119
|
+
readonly queueSetupPromises: QueueSetupPromisesDictionary;
|
|
120
|
+
readonly assertExchangePromises: AssertExchangePromisesDictionary;
|
|
100
121
|
readonly redisClient?: ReturnType<typeof getRedisInstance>;
|
|
101
122
|
readonly redisLock?: ReturnType<typeof RedisLock>;
|
|
102
|
-
|
|
123
|
+
/** A map of consumers' tags used for canceling consumption */
|
|
124
|
+
readonly consumersTags: Map<string, {
|
|
125
|
+
channel: ConfirmChannel;
|
|
126
|
+
consumerTag: string;
|
|
127
|
+
}>;
|
|
128
|
+
private readonly consumersToRegister;
|
|
103
129
|
private doesVHostExist;
|
|
104
130
|
private readonly vhost;
|
|
105
131
|
private gracefulShutdownStarted;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
132
|
+
shutdownAbortController: AbortController;
|
|
133
|
+
oldPublishChannel: ChannelWrapper | null;
|
|
134
|
+
oldPublishChannelSetupPromise: Promise<ChannelWrapper> | null;
|
|
135
|
+
readonly oldPublishConnection: ConnectionData;
|
|
136
|
+
readonly oldConsumeConnection: ConnectionData;
|
|
137
|
+
readonly oldEm: EventEmitter;
|
|
138
|
+
readonly oldExchanges: ExchangesCache;
|
|
139
|
+
readonly oldQueues: QueuesCache;
|
|
140
|
+
readonly oldQueueSetupPromises: QueueSetupPromisesDictionary;
|
|
141
|
+
readonly oldAssertExchangePromises: AssertExchangePromisesDictionary;
|
|
142
|
+
readonly oldConsumersTags: Map<string, {
|
|
143
|
+
channel: ConfirmChannel;
|
|
144
|
+
consumerTag: string;
|
|
145
|
+
}>;
|
|
146
|
+
private readonly oldConsumersToRegister;
|
|
117
147
|
constructor(options?: AfRabbitOptions, redisConfig?: RedisConfig | undefined);
|
|
118
148
|
private getRedisKey;
|
|
119
|
-
private isQuorumQueue;
|
|
120
|
-
private getVhostForQueue;
|
|
121
149
|
private assertVHost;
|
|
122
150
|
private shouldConsumeMessageByTimestamp;
|
|
123
151
|
ack: (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp?: boolean, releaseLock?: (() => Promise<void>) | null) => (userMsg: ConsumeMessage) => Promise<void>;
|
|
124
152
|
nack: (channel: ConfirmChannel, queue: string, options: ConsumeOptions, deadQueueOptions: Options.AssertQueue, msg: ConsumeMessageOrNull, releaseLock?: (() => Promise<void>) | null) => (userMsg: ConsumeMessageOrNull, {
|
|
125
153
|
skipRetry
|
|
126
154
|
}?: NackOptions) => Promise<void>;
|
|
127
|
-
getConnection(connection: ConnectionData
|
|
128
|
-
getConnectionForVhost(vhost: string, type: "publish" | "consume"): Promise<AmqpConnectionManager>;
|
|
129
|
-
getConnectionDataForVhost(vhost: string, type: "publish" | "consume"): ConnectionData;
|
|
130
|
-
assertChannelForVhost(vhost: string, force?: boolean): Promise<ChannelWrapper>;
|
|
155
|
+
getConnection(connection: ConnectionData): Promise<AmqpConnectionManager>;
|
|
131
156
|
getNewChannel({
|
|
132
157
|
name,
|
|
133
158
|
onClose,
|
|
134
159
|
options,
|
|
135
160
|
connection
|
|
136
161
|
}: newChannelOpts): Promise<ChannelWrapper>;
|
|
137
|
-
|
|
162
|
+
assertChannel({
|
|
163
|
+
force,
|
|
164
|
+
connection
|
|
165
|
+
}: assertChannelOpts): Promise<ChannelWrapper>;
|
|
166
|
+
assertExchange(exchangeName: string, connection: ConnectionData): Promise<Replies.AssertExchange>;
|
|
138
167
|
getQueueLength(queue: string): Promise<Replies.AssertQueue>;
|
|
139
168
|
private deleteQueue;
|
|
140
169
|
bindQueue(queue: string, exchange: string): Promise<void>;
|
|
141
|
-
setupQueue(queueName: string, options?: Options.AssertQueue
|
|
170
|
+
setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue>;
|
|
171
|
+
static shouldUseQuorum(queueName: string): boolean;
|
|
142
172
|
assertQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue>;
|
|
143
173
|
private saveConsumer;
|
|
144
174
|
consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<void>;
|
|
175
|
+
consumeNew(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<void>;
|
|
176
|
+
consumeOld(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<void>;
|
|
145
177
|
private lockRedisIfNeeded;
|
|
146
178
|
private unlockRedisIfNeeded;
|
|
147
179
|
private consumeFromRabbit;
|
|
148
180
|
consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<void>;
|
|
149
|
-
publish(exchange: string, content: any, customHeaders?: PublishOptions["headers"],
|
|
150
|
-
sendToQueue(queue: string, content: any, options?: Options.AssertQueue, customHeaders?: PublishOptions["headers"],
|
|
181
|
+
publish(exchange: string, content: any, customHeaders?: PublishOptions["headers"], isQuorumQueue?: boolean): Promise<void>;
|
|
182
|
+
sendToQueue(queue: string, content: any, options?: Options.AssertQueue, customHeaders?: PublishOptions["headers"], isQuorumQueue?: boolean): Promise<boolean | undefined>;
|
|
151
183
|
isConnected(): Promise<boolean>;
|
|
152
184
|
gracefulShutdown(signal: string): Promise<void>;
|
|
185
|
+
private consumeFromRabbitOld;
|
|
186
|
+
getNewChannelOld({
|
|
187
|
+
name,
|
|
188
|
+
onClose,
|
|
189
|
+
options,
|
|
190
|
+
connection
|
|
191
|
+
}: newChannelOpts): Promise<ChannelWrapper>;
|
|
192
|
+
getConnectionOld(connection: ConnectionData): Promise<AmqpConnectionManager>;
|
|
193
|
+
private saveConsumerOld;
|
|
194
|
+
assertQueueOld(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue>;
|
|
195
|
+
setupQueueOld(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue>;
|
|
196
|
+
assertChannelOld({
|
|
197
|
+
force,
|
|
198
|
+
connection
|
|
199
|
+
}: assertChannelOpts): Promise<ChannelWrapper>;
|
|
200
|
+
private deleteQueueOld;
|
|
201
|
+
assertExchangeOld(exchangeName: string, connection: ConnectionData): Promise<Replies.AssertExchange>;
|
|
202
|
+
isConnectedOld(): Promise<boolean>;
|
|
153
203
|
private maskURL;
|
|
154
204
|
}
|
|
155
205
|
//#endregion
|
|
156
206
|
export { sendCeleryTaskViaHttp as i, IAfRabbitMq as n, RabbitMq as r, AfRabbitOptions as t };
|
|
157
|
-
//# sourceMappingURL=index-
|
|
207
|
+
//# sourceMappingURL=index-HKpuFnj9.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:timers/promises`),l=require(`node:events`),u=require(`moment`);u=s(u);let d=require(`redis-lock`);d=s(d);let f=require(`amqp-connection-manager`),p=require(`exponential-backoff`),m=require(`@autofleet/zehut`),h=require(`node:crypto`),g=require(`@autofleet/logger`);g=s(g);let _=require(`redis`);const v=(0,g.default)();var y=v,b=class extends Error{constructor(e){super(e),this.name=`RabbitError`}};const x=e=>(0,_.createClient)({socket:e});var S=x;const C=6e4*60*12,w=1e3*5,T=`x-retry-count`,E=`x-trace-id`,D=`x-af-user-id`,O=`x-af-automation-id`,k=!1,A={limit:1,retries:1,deadMessageTtl:432e5,lockTimeout:w,useConsumeWithLock:!1,auditContext:null,enableRabbitTrace:!1},j={startingDelay:500,timeMultiple:4,numOfAttempts:5},M={startingDelay:1,timeMultiple:1,numOfAttempts:5},{PROJECT_ID:N}=process.env,P=async(e,t)=>e.assertExchange(t,`fanout`),F=()=>Math.floor(Math.random()*1e5),I=()=>{if(Promise.withResolvers)return Promise.withResolvers();let e,t;return{promise:new Promise((n,r)=>{e=n,t=r}),resolve:e,reject:t}},L=()=>[`af-experiment-manager`,`dev1-experiment-manager`].includes(N||``),R=()=>L()?j:M,z=`ha-promote-on-failure`,B=`ha-promote-on-shutdown`,V={arguments:{"ha-promote-on-failure":`always`,"ha-promote-on-shutdown":`always`}},H={host:process.env.RABBITMQ_SERVICE_HOST||`localhost`,username:process.env.RABBITMQ_USERNAME||`guest`,password:process.env.RABBITMQ_PASSWORD||`guest`};async function U(e,{taskName:t,queueName:n}){let r=`http://${H.host}:15672/api/exchanges/%2f/amq.default/publish`,i={task:t,id:(0,h.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(`${H.username}:${H.password}`).toString(`base64`)}`},body:JSON.stringify(a)});if(e.ok){let t=await e.json();y.info(`Successfully published message:`,t)}else y.error(`Failed to publish message. Status code: ${e.status}`),y.error(`Response: ${await e.text()}`)}catch(e){throw y.error(`Error sending request:`,e instanceof Error?e.message:String(e)),e}}const W=1e3*10,G=`60`;var K=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 b(`error while using ${e} with no name`)}static getPublishOptions(e={}){let t=(0,m.getUser)(),n=m.outbreak.getCurrentContextTraceId();return{timestamp:(0,u.default)().unix(),timeout:1e4,headers:{creationTimestamp:(0,u.default)().valueOf(),...e,[D]:t?.id,[m.CONTEXTS_IDS_HEADER]:t?.contextIds,[E]:n}}}#e;constructor(t={},n){this.options=t,this.redisConfig=n,this.DISCONNECT_MSG=`rabbit: connection disconnect`,this.RECONNECT_MSG=`rabbit: connection disconnect - reconnecting`,this.em=new l.EventEmitter,this.doesVHostExist=!1,this.vhost=`quorum-vhost`,this.gracefulShutdownStarted=!1,this.publishConnections=new Map,this.consumeConnections=new Map,this.publishChannels=new Map,this.publishChannelSetupPromises=new Map,this.queuesByVhost=new Map,this.exchangesByVhost=new Map,this.queueSetupPromisesByVhost=new Map,this.assertExchangePromisesByVhost=new Map,this.consumersTagsByVhost=new Map,this.consumersToRegisterByVhost=new Map,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.#e.info(`Vhost exists`,{vhost:this.vhost});return}if(e.status!==404)throw this.#e.error(`Failed to check vhost`,{response:e}),new b(`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.#e.error(`Failed to create vhost`,{response:t}),new b(`Failed to create vhost`);this.doesVHostExist=!0,this.#e.info(`Vhost created`,{vhost:this.vhost})}catch(e){throw this.#e.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.#e.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}={})=>{if(await this.unlockRedisIfNeeded(o),!t||!a){this.#e.error(`no channel or msg`,{msg:a});return}let l=Number.parseInt(a.properties.headers?.[T]||`0`,10)||0,u=c||l>=r.retries;await this.sendToQueue(`${n}${u?`-dead`:``}`,e.parseMsg(a).content,u?i:r,{...a.properties.headers,[T]:l+1}),this.#e.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.#e=t?.logger??y;let r=process.env.QUORUM_QUEUES||``;this.quorumQueues=new Set(r.split(`,`).map(e=>e.trim()).filter(Boolean)),n&&(this.redisClient=S(n).on(`error`,e=>{this.#e.error(`rabbit: Redis error`,{err:e,redisConfig:n})}),this.redisClient.connect().catch(e=>{this.#e.error(`rabbit: Failed to connect to Redis`,{err:e,redisConfig:n})}),this.redisLock=(0,d.default)(this.redisClient)),this.#e.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}`}isQuorumQueue(e){return this.quorumQueues.has(e)}getVhostForQueue(e){return this.isQuorumQueue(e)?`quorum-vhost`:``}async getConnection(e,t){let{amqpConnection:n,creatingConnection:r,connectionCreatedEventName:i,connectionFailedEventName:a,blockReconnect:o}=e;if(o){this.#e.debug(`rabbit: block reconnect`);return}if(n!==null){if(this.options?.disableReconnect||n?.isConnected())return this.#e.debug(`rabbit: connection - is connected`),n;this.#e.debug(`rabbit: connection - reconnecting`)}if(r){this.#e.debug(`rabbit: creating connection emi`);let[e,t]=await Promise.race([i,a].map(e=>(0,l.once)(this.em,e).then(([t])=>[e,t])));if(e===i)return t;throw t}e.creatingConnection=!0;let s=!1,c=()=>{let e=process.env.RABBITMQ_USERNAME||`guest`,n=process.env.RABBITMQ_PASSWORD||`guest`,r=this.options?.rabbitHost||process.env.RABBITMQ_SERVICE_HOST||`localhost`,i=t??this.vhost;return this.#e.debug(`rabbit: creating connection`,{host:r,userName:e,HEARTBEAT:`60`,vhost:i}),[`amqp://${e}:${n}@${r}/${i}?heartbeat=60`]},u=(0,f.connect)(c(),{findServers:c});e.amqpConnection=u;let{promise:d,reject:p,resolve:m}=I();return u.on(`error`,e=>{this.#e.error(`rabbit: connection error`,{err:e}),s||(s=!0,p(e),this.em.emit(a,e))}),u.on(`connectFailed`,e=>{for(let e of this.consumersTagsByVhost.values())e.clear();typeof e.url==`string`&&(e.url=this.maskURL(e.url)),this.#e.error(`rabbit: connection connectFailed`,{err:e,advice:`Check if the vhost exist`,vhost:this.vhost}),s||(s=!0,p(e),this.em.emit(a,e))}),u.on(`disconnect`,({err:t})=>{for(let e of this.consumersTagsByVhost.values())e.clear();this.#e.debug(`rabbit: connection closed`),this.options?.disableReconnect?(this.#e.error(`${this.DISCONNECT_MSG}${t&&` - ${t}`}`),e.blockReconnect=!0):this.#e.error(`${this.RECONNECT_MSG}${t&&` - ${t}`}`)}),u.once(`connect`,async()=>{this.#e.debug(`rabbit: connection established`),e.creatingConnection=!1,this.em.emit(i,u),s=!0,m(u)}),d}async getConnectionForVhost(e,t){let n=t===`publish`?this.publishConnections:this.consumeConnections;if(!n.has(e)){let r={amqpConnection:null,creatingConnection:!1,connectionCreatedEventName:`${t}ConnectionCreated-${e||`default`}`,connectionFailedEventName:`${t}ConnectionFailed-${e||`default`}`,blockReconnect:!1};n.set(e,r)}let r=n.get(e);return this.getConnection(r,e)}getConnectionDataForVhost(e,t){let n=t===`publish`?this.publishConnections:this.consumeConnections;if(!n.has(e)){let r={amqpConnection:null,creatingConnection:!1,connectionCreatedEventName:`${t}ConnectionCreated-${e||`default`}`,connectionFailedEventName:`${t}ConnectionFailed-${e||`default`}`,blockReconnect:!1};n.set(e,r)}return n.get(e)}async assertChannelForVhost(e,t=!1){let n=this.publishChannels.get(e),r=this.publishChannelSetupPromises.get(e);if(!t&&n)return n;if(!t&&r)return r;let i=(async()=>{let t=this.getConnectionDataForVhost(e,`publish`),n=await this.getNewChannel({connection:t});return this.publishChannels.set(e,n),this.publishChannelSetupPromises.set(e,null),n})();return this.publishChannelSetupPromises.set(e,i),i}async getNewChannel({name:e=F().toString(),onClose:t=null,options:n={},connection:r}){let i;try{i=await this.getConnection(r)}catch(t){throw this.#e.error(`rabbit: error on get connection for new channel ${e} `,{e:t}),t}let a=i?.createChannel({...n});(0,l.once)(a,`close`).then(n=>{this.#e.error(`rabbit: channel ${e} closed`),t?.(n)});try{return await(0,l.once)(a,`connect`),this.#e.debug(`rabbit: channel ${e} CONNECTED`),a}catch(t){throw this.#e.error(`rabbit: channel error ${e} error`,{err:t}),t}}async assertExchange(e,t=``){let n=await this.assertChannelForVhost(t);this.exchangesByVhost.has(t)||this.exchangesByVhost.set(t,{});let r=this.exchangesByVhost.get(t);if(r[e]){this.assertExchangePromisesByVhost.has(t)||this.assertExchangePromisesByVhost.set(t,{});let n=this.assertExchangePromisesByVhost.get(t);return delete n[e],r[e]}this.assertExchangePromisesByVhost.has(t)||this.assertExchangePromisesByVhost.set(t,{});let i=this.assertExchangePromisesByVhost.get(t);return i[e]?i[e]:(i[e]=P(n,e),r[e]=await i[e],r[e])}async getQueueLength(t){e.validateName(`queue`,t);let n=this.getVhostForQueue(t),r=this.publishChannels.get(n);if(!r)throw new b(`channel is not defined`);let i=this.getConnectionDataForVhost(n,`publish`);return this.#e.debug(`rabbit: getting queue length`,{queue:t,connected:i.amqpConnection?.isConnected()}),r.checkQueue(t)}async deleteQueue(t){e.validateName(`queue`,t);let n=this.getVhostForQueue(t),r=await this.assertChannelForVhost(n);this.#e.info(`rabbit: deleting queue`,{queue:t});let i=await r.deleteQueue(t);return this.#e.debug(`queue deleted`,i),i}async bindQueue(e,t){let n=this.getVhostForQueue(e),r=await this.assertChannelForVhost(n);return await r.addSetup(n=>n.bindQueue(e,t,``)),r.bindQueue(e,t,``)}async setupQueue(e,t,n){let r,i=n??this.getVhostForQueue(e),a=this.isQuorumQueue(e),o={...t,durable:!0,arguments:{...t?.arguments,"x-consumer-timeout":1e3*60*60*24,"x-queue-type":a?`quorum`:`classic`}};try{let t=await this.assertChannelForVhost(i);this.#e.debug(`assertQueue->channel.addSetup`,{queueName:e,vhost:i,queueType:a?`quorum`:`classic`}),await t.addSetup(async t=>{await t.assertQueue(e,o)}),this.#e.debug(`assertQueue->channel.assertQueue`,{queueName:e}),r=await t.assertQueue(e,o)}catch(n){if(this.#e.error(`rabbit: assertQueue error`,{queueName:e,options:t,error:n}),this.options?.dontRetryAssert)throw n;{this.#e.debug(`retrying assertQueue`,{queueName:e});let t=await this.assertChannelForVhost(i,!0);await this.deleteQueue(e),this.#e.debug(`retrying assertQueue->channel.addSetup`,{queueName:e}),await t.addSetup(t=>t.assertQueue(e,o)),this.#e.debug(`retrying assertQueue->channel.assertQueue`,{queueName:e}),r=await t.assertQueue(e,o)}}this.queuesByVhost.has(i)||this.queuesByVhost.set(i,{});let s=this.queuesByVhost.get(i);return s[e]=r,r}async assertQueue(t,n){e.validateName(`queue`,t);let r=this.getVhostForQueue(t);this.queuesByVhost.has(r)||this.queuesByVhost.set(r,{});let i=this.queuesByVhost.get(r);if(i[t]){this.queueSetupPromisesByVhost.has(r)||this.queueSetupPromisesByVhost.set(r,{});let e=this.queueSetupPromisesByVhost.get(r);return delete e[t],i[t]}this.queueSetupPromisesByVhost.has(r)||this.queueSetupPromisesByVhost.set(r,{});let a=this.queueSetupPromisesByVhost.get(r);return a[t]??=this.setupQueue(t,n,r),a[t]}saveConsumer(e,t,n,r){this.consumersToRegisterByVhost.has(r)||this.consumersToRegisterByVhost.set(r,[]);let i=this.consumersToRegisterByVhost.get(r);i.some(t=>t.queue===e)||(this.#e.info(`rabbit: consumer: ${e} saved in consumer array for vhost: ${r||`default`}`),i.push({queue:e,callback:t,options:n}))}async consume(e,t,n){let r=this.getVhostForQueue(e);this.saveConsumer(e,t,n,r),r===`quorum-vhost`&&await(0,p.backOff)(()=>this.assertVHost(),R()),await this.consumeFromRabbit(e,t,n,r)}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||w)),i}async unlockRedisIfNeeded(e){this.redisLock&&e&&await e()}async consumeFromRabbit(t,n,r,i){let a={...A,...r};e.validateName(`queue`,t);let o=(0,h.randomUUID)(),{limit:s,deadMessageTtl:c,useConsumeWithLock:l,lockTimeout:u,auditContext:d,enableRabbitTrace:f}=a;if(l){if(!this.redisLock)throw new b(`Usage of consumeWithLock requires RedisInstance`);this.#e.info(`rabbit: Consuming with lock from queue ${t} with lockTimeout: ${u}ms`)}let p=this.getConnectionDataForVhost(i,`consume`);return(await this.getNewChannel({connection:p})).addSetup(async r=>{await this.assertQueue(t,a),await r.prefetch(s,!1);let{consumerTag:l}=await r.consume(t,async i=>{if(!i)return null;let{[E]:s,[D]:l,"x-af-automation-id":u,[m.CONTEXTS_IDS_HEADER]:p}=i.properties.headers??{},h=e.parseMsg(i),g=await this.lockRedisIfNeeded(h,a),_=(0,m.newTrace)(m.traceTypes.RABBIT);if(l&&f)try{await(0,m.createOrSetRabbitTrace)(_,l,p)}catch(e){return this.#e.error(`rabbit: failed to setRabbitTrace`,{userId:l,e}),this.nack(r,t,a,{messageTtl:c},i,g)(i)}if(s&&_.context?.set(E,s),d&&await d(t,{userId:l,automationId:u}),!await this.shouldConsumeMessageByTimestamp(h))return await this.unlockRedisIfNeeded(g),this.ack(r,i)(i);let v=!1,y=async()=>{v||(v=!0,await this.ack(r,i,!0,g)(i))},b=async(e,n={})=>{v||(this.#e.debug(`rabbit localNack`,{messageAcked:v,uniqueId:o,deliveryTag:i.fields.deliveryTag}),v=!0,await this.nack(r,t,a,{messageTtl:c},i,g)(i,n))};try{return await n(h,y,b)}catch{return b(i)}},V);l?(this.#e.info(`rabbit: adding tag ${l} to the array for vhost ${i||`default`}.`),this.consumersTagsByVhost.has(i)||this.consumersTagsByVhost.set(i,new Map),this.consumersTagsByVhost.get(i).set(t,{channel:r,consumerTag:l})):this.#e.error(`rabbit: failed to consume from queue ${t}`)})}async consumeFromExchange(t,n,r,i){let a={...A,...i},o=this.getVhostForQueue(t);e.validateName(`exchange`,n),e.validateName(`queue`,t);let{limit:s}=a;this.saveConsumer(t,r,i,o),o===`quorum-vhost`&&await(0,p.backOff)(()=>this.assertVHost(),R());let c=this.getConnectionDataForVhost(o,`consume`);await(await this.getNewChannel({name:`consume-exchange-${n}-queue-${t}`,connection:c})).addSetup(async e=>{let r=await P(e,n);await e.assertQueue(t),this.exchangesByVhost.has(o)||this.exchangesByVhost.set(o,{});let i=this.exchangesByVhost.get(o);i[n]=r,await e.prefetch(s,!1),await e.bindQueue(t,n,``)}),await this.consume(t,r,i)}async publish(t,n,r,i=!0){await(0,c.setImmediate)(),e.validateName(`exchange`,t);let a=await this.assertChannelForVhost(``);await this.assertExchange(t,``),await a.publish(t,``,Buffer.from(JSON.stringify(n)),e.getPublishOptions(r))}async sendToQueue(t,n,r,i,a=!0){let o=this.getVhostForQueue(t);try{o===`quorum-vhost`&&await this.assertVHost(),await this.assertChannelForVhost(o)}catch(e){throw this.#e.error(`rabbit sendToQueue: failed to assert channel when sending to queue ${t}`,{e}),e}try{e.validateName(`queue`,t),await this.assertQueue(t,r)}catch(e){throw this.#e.error(`rabbit sendToQueue: failed to assert queue ${t}`,{e}),e}try{let r=await this.publishChannels.get(o)?.sendToQueue(t,Buffer.from(JSON.stringify(n)),e.getPublishOptions(i));return this.#e.debug(`rabbit: sending to queue ${t} on vhost ${o||`default`}`,{res:r}),r}catch(e){let n=await this.isConnected();throw this.#e.error(`rabbit sendToQueue: failed to send to queue ${t}, isConnected: ${n}`,{e}),e}}async isConnected(){if(this.gracefulShutdownStarted)return!1;try{this.#e.debug(`rabbit: checking connection status`);let e=[...this.publishConnections.values(),...this.consumeConnections.values()];if(e.length===0)return this.#e.debug(`rabbit: no connections established yet`),!0;if(!(await Promise.all(e.map(e=>this.getConnection(e)))).every(e=>e.isConnected()))return this.#e.error(`rabbit: isConnected - some connections are not active`),!1;for(let[e,t]of this.consumersToRegisterByVhost){let n=this.consumersTagsByVhost.get(e),r=t.filter(e=>!n?.get(e.queue));if(r.length>0){let t=r.map(e=>e.queue);return this.#e.error(`rabbit: found unregistered consumers for queues`,{vhost:e||`default`,count:t.length,queues:t}),!1}let i=await this.assertChannelForVhost(e);await i.waitForConnect(),await Promise.all(t.map(e=>i.checkQueue(e.queue)))}return this.#e.debug(`rabbit: all connections and consumers are healthy`),!0}catch(e){return this.#e.error(`rabbit: isConnected - false`,{msg:e.message}),!1}}async gracefulShutdown(e){this.gracefulShutdownStarted=!0;let t=0;for(let e of this.consumersTagsByVhost.values())t+=e.size;this.#e.info(`rabbit: [gracefully-shutdown] received ${e}! canceling #${t} consumers across ${this.consumersTagsByVhost.size} vhosts...`);let n=[];for(let[e,t]of this.consumersTagsByVhost){for(let[r,{channel:i,consumerTag:a}]of t)this.#e.debug(`rabbit: [gracefully-shutdown] canceling consumer ${a} for queue ${r} on vhost ${e||`default`}`),n.push(i.cancel(a));t.clear()}let r=(await Promise.allSettled(n)).filter(e=>e.status===`rejected`);r.length>0?this.#e.warn(`rabbit: [gracefully-shutdown] #${r.length}/${n.length} consumers failed to cancel`):this.#e.info(`rabbit: [gracefully-shutdown] all consumers successfully canceled.`);let i=[...this.publishConnections.values(),...this.consumeConnections.values()];await Promise.all(i.map(e=>e.amqpConnection?.close()).filter(e=>e!==void 0)),this.#e.info(`rabbit: [gracefully-shutdown] all connections closed.`)}},q=K;exports.default=q,exports.sendCeleryTaskViaHttp=U,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=!1,j={limit:1,retries:1,deadMessageTtl:432e5,lockTimeout:T,useConsumeWithLock:!1,auditContext:null,enableRabbitTrace:!1},M={startingDelay:500,timeMultiple:4,numOfAttempts:5},N={startingDelay:1,timeMultiple:1,numOfAttempts:5},{PROJECT_ID:P}=process.env,F=async(e,t)=>e.assertExchange(t,`fanout`),I=()=>Math.floor(Math.random()*1e5),L=()=>{if(Promise.withResolvers)return Promise.withResolvers();let e,t;return{promise:new Promise((n,r)=>{e=n,t=r}),resolve:e,reject:t}},R=()=>[`af-experiment-manager`,`dev1-experiment-manager`].includes(P||``),z=()=>R()?M:N,B=`ha-promote-on-failure`,V=`ha-promote-on-shutdown`,H={arguments:{"ha-promote-on-failure":`always`,"ha-promote-on-shutdown":`always`}},U={host:process.env.RABBITMQ_SERVICE_HOST||`localhost`,username:process.env.RABBITMQ_USERNAME||`guest`,password:process.env.RABBITMQ_PASSWORD||`guest`};async function W(e,{taskName:t,queueName:n}){let r=`http://${U.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(`${U.username}:${U.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 G=1e3*10,{DISABLE_QUORUM_QUEUES_CONSUME:K,DISABLE_QUORUM_QUEUES_PUBLISH:q}=c.env,J=`60`;var Y=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,[O]:t?.id,[h.CONTEXTS_IDS_HEADER]:t?.contextIds,[D]:n}}}#e;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.doesVHostExist=!1,this.vhost=`quorum-vhost`,this.gracefulShutdownStarted=!1,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.#e.info(`Vhost exists`,{vhost:this.vhost});return}if(e.status!==404)throw this.#e.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.#e.error(`Failed to create vhost`,{response:t}),new x(`Failed to create vhost`);this.doesVHostExist=!0,this.#e.info(`Vhost created`,{vhost:this.vhost})}catch(e){throw this.#e.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.#e.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}={})=>{if(await this.unlockRedisIfNeeded(o),!t||!a){this.#e.error(`no channel or msg`,{msg:a});return}let l=Number.parseInt(a.properties.headers?.[E]||`0`,10)||0,u=c||l>=r.retries;await this.sendToQueue(`${n}${u?`-dead`:``}`,e.parseMsg(a).content,u?i:r,{...a.properties.headers,[E]:l+1}),this.#e.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.#e=t?.logger??b,n&&(this.redisClient=C(n).on(`error`,e=>{this.#e.error(`rabbit: Redis error`,{err:e,redisConfig:n})}),this.redisClient.connect().catch(e=>{this.#e.error(`rabbit: Failed to connect to Redis`,{err:e,redisConfig:n})}),this.redisLock=(0,f.default)(this.redisClient)),this.#e.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}`}async getConnection(e){let{amqpConnection:t,creatingConnection:n,connectionCreatedEventName:r,connectionFailedEventName:i,blockReconnect:a}=e;if(a){this.#e.debug(`rabbit: block reconnect`);return}if(t!==null){if(this.options?.disableReconnect||t?.isConnected())return this.#e.debug(`rabbit: connection - is connected`),t;this.#e.debug(`rabbit: connection - reconnecting`)}if(n){this.#e.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.#e.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}=L();return c.on(`error`,e=>{this.#e.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.#e.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.#e.debug(`rabbit: connection closed`),this.options?.disableReconnect?(this.#e.error(`${this.DISCONNECT_MSG}${t&&` - ${t}`}`),e.blockReconnect=!0):this.#e.error(`${this.RECONNECT_MSG}${t&&` - ${t}`}`)}),c.once(`connect`,async()=>{this.#e.debug(`rabbit: connection established`),e.creatingConnection=!1,this.em.emit(r,c),o=!0,f(c)}),l}async getNewChannel({name:e=I().toString(),onClose:t=null,options:n={},connection:r}){let i;try{i=await this.getConnection(r)}catch(t){throw this.#e.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.#e.error(`rabbit: channel ${e} closed`),t?.(n)});try{return await(0,u.once)(a,`connect`),this.#e.debug(`rabbit: channel ${e} CONNECTED`),a}catch(t){throw this.#e.error(`rabbit: channel error ${e} error`,{err:t}),t}}async assertChannel({force:e=!1,connection:t}){if(this.publishChannelSetupPromise)return this.publishChannelSetupPromise;let{promise:n,resolve:r,reject:i}=L();if(this.publishChannelSetupPromise=n,this.publishChannel&&!e)return r(this.publishChannel),n;try{let e=await this.getNewChannel({connection:t});e.on(`error`,e=>{this.#e.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]=F(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.#e.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.#e.info(`rabbit: deleting queue`,{queue:t});let i=await r.deleteQueue(t);return this.#e.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.#e.debug(`assertQueue->channel.addSetup`,{queueName:e}),await t.addSetup(async t=>{await t.assertQueue(e,i)}),this.#e.debug(`assertQueue->channel.assertQueue`,{queueName:e}),n=await t.assertQueue(e,i)}catch(a){if(this.#e.error(`rabbit: assertQueue error`,{queueName:e,options:t,error:a}),this.options?.dontRetryAssert)throw a;{this.#e.debug(`retrying assertQueue`,{queueName:e});let t=await this.assertChannel({force:!0,connection:r});await this.deleteQueue(e,r),this.#e.debug(`retrying assertQueue->channel.addSetup`,{queueName:e}),await t.addSetup(t=>t.assertQueue(e,i)),this.#e.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.#e.info(`rabbit: consumer: ${e} saved in consumer array`),this.consumersToRegister.push({queue:e,callback:t,options:n}))}async consume(e,t,n){this.saveConsumerOld(e,t,n),n?.isQuorumQueue!==!1&&K!==`true`&&(this.saveConsumer(e,t,n),await(0,m.backOff)(()=>this.assertVHost(),z()),await this.consumeNew(e,t,n)),await this.consumeOld(e,t,n)}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={...j,...r};e.validateName(`queue`,t);let a=(0,g.randomUUID)(),{limit:o,deadMessageTtl:s,useConsumeWithLock:c,lockTimeout:l,auditContext:u,enableRabbitTrace:d}=i;if(c){if(!this.redisLock)throw new x(`Usage of consumeWithLock requires RedisInstance`);this.#e.info(`rabbit: Consuming with lock from queue ${t} with lockTimeout: ${l}ms`)}return(await this.getNewChannel({connection:this.consumeConnection})).addSetup(async r=>{await this.assertQueue(t,i),await r.prefetch(o,!1);let{consumerTag:c}=await r.consume(t,async o=>{if(!o)return null;let{[D]:c,[O]:l,[k]:f,[h.CONTEXTS_IDS_HEADER]:p}=o.properties.headers??{},m=e.parseMsg(o),g=await this.lockRedisIfNeeded(m,i),_=(0,h.newTrace)(h.traceTypes.RABBIT);if(l&&d)try{await(0,h.createOrSetRabbitTrace)(_,l,p)}catch(e){return this.#e.error(`rabbit: failed to setRabbitTrace`,{userId:l,e}),this.nack(r,t,i,{messageTtl:s},o,g)(o)}if(c&&_.context?.set(D,c),u&&await u(t,{userId:l,automationId:f}),!await this.shouldConsumeMessageByTimestamp(m))return await this.unlockRedisIfNeeded(g),this.ack(r,o)(o);let v=!1,y=async()=>{v||(v=!0,await this.ack(r,o,!0,g)(o))},b=async(e,n={})=>{v||(this.#e.debug(`rabbit localNack`,{messageAcked:v,uniqueId:a,deliveryTag:o.fields.deliveryTag}),v=!0,await this.nack(r,t,i,{messageTtl:s},o,g)(o,n))};try{return await n(m,y,b,this.shutdownAbortController.signal)}catch{return b(o)}},H);c?(this.#e.info(`rabbit: adding tag ${c} to the array.`),this.consumersTags.set(t,{channel:r,consumerTag:c})):this.#e.error(`rabbit: failed to consume from queue ${t}`)})}async consumeFromExchange(t,n,r,i){let a={...j,...i};e.validateName(`exchange`,n),e.validateName(`queue`,t);let{limit:o}=a;i?.isQuorumQueue!==!1&&K!==`true`&&(await this.saveConsumer(t,r,i),await(0,m.backOff)(()=>this.assertVHost(),z()),await(await this.getNewChannel({name:`consume-exchange-${n}-queue-${t}`,connection:this.consumeConnection})).addSetup(async e=>{let a=await F(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 F(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)])})}async publish(t,n,r,i=!0){if(await(0,l.setImmediate)(),i&&q!==`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&&q!==`true`){try{await this.assertVHost(),await this.assertChannel({connection:this.publishConnection})}catch(e){throw this.#e.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.#e.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.#e.debug(`rabbit: sending to queue ${t}`,{res:r}),r}catch(e){let n=await this.isConnected();throw this.#e.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.#e.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.#e.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.#e.debug(`rabbit: sending to queue ${t}`,{res:r}),r}catch(e){let n=await this.isConnectedOld();throw this.#e.error(`rabbit sendToQueue: failed to send to queue ${t}, isConnected: ${n}`,{e}),e}}}async isConnected(){let e=!0;if(!this.gracefulShutdownStarted){if(K!==`true`||q!==`true`){this.#e.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.#e.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.#e.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.#e.error(`rabbit: isConnected - false`,{msg:e.message}),!1}}e=await this.isConnectedOld()}return this.#e.debug(`rabbit: isConnected - ${e}`),e}async gracefulShutdown(e){this.shutdownAbortController.signal.aborted||this.shutdownAbortController.abort(Error(`Graceful shutdown initiated by signal: ${e}`)),this.gracefulShutdownStarted=!0;let t=this.consumersTags.size+this.oldConsumersTags.size;this.#e.info(`rabbit: [gracefully-shutdown] received ${e}! canceling #${t} tags...`);let n=[...this.consumersTags].map(([,{channel:e,consumerTag:t}])=>e.cancel(t)),r=[...this.oldConsumersTags].map(([,{channel:e,consumerTag:t}])=>e.cancel(t));this.consumersTags.clear(),this.oldConsumersTags.clear();let i=(await Promise.allSettled([...n,...r])).filter(e=>e.status===`rejected`);i.length>0?this.#e.warn(`rabbit: [gracefully-shutdown] #${i.length}/${t} tags failed to cancel: ${i}`):this.#e.info(`rabbit: [gracefully-shutdown] all tags successfully canceled.`)}async consumeFromRabbitOld(t,n,r){let i={...j,...r};e.validateName(`queue`,t);let a=(0,g.randomUUID)(),{limit:o,deadMessageTtl:s,useConsumeWithLock:c,lockTimeout:l,auditContext:u,enableRabbitTrace:d}=i;if(c){if(!this.redisLock)throw new x(`Usage of consumeWithLock requires RedisInstance`);this.#e.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{[D]:c,[O]:l,[k]:f,[h.CONTEXTS_IDS_HEADER]:p}=o.properties.headers??{},m=e.parseMsg(o),g=await this.lockRedisIfNeeded(m,i),_=(0,h.newTrace)(h.traceTypes.RABBIT);if(l&&d)try{await(0,h.createOrSetRabbitTrace)(_,l,p)}catch(e){return this.#e.error(`rabbit: failed to setRabbitTrace`,{userId:l,e}),this.nack(r,t,i,{messageTtl:s},o,g)(o)}if(c&&_.context?.set(D,c),u&&await u(t,{userId:l,automationId:f}),!await this.shouldConsumeMessageByTimestamp(m))return await this.unlockRedisIfNeeded(g),this.ack(r,o)(o);let v=!1,y=async()=>{v||(v=!0,await this.ack(r,o,!0,g)(o))},b=async(e,n={})=>{v||(this.#e.debug(`rabbit localNack`,{messageAcked:v,uniqueId:a,deliveryTag:o.fields.deliveryTag}),v=!0,await this.nack(r,t,i,{messageTtl:s},o,g)(o,n))};try{return await n(m,y,b,this.shutdownAbortController.signal)}catch{return b(o)}},H);c?(this.#e.info(`rabbit: adding tag ${c} to the array old`),this.oldConsumersTags.set(t,{channel:r,consumerTag:c})):this.#e.error(`rabbit: failed to consume from queue ${t} old`)})}async getNewChannelOld({name:e=I().toString(),onClose:t=null,options:n={},connection:r}){let i;try{i=await this.getConnectionOld(r)}catch(t){throw this.#e.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.#e.error(`rabbit: channel ${e} closed`),t?.(n)});try{return await(0,u.once)(a,`connect`),this.#e.debug(`rabbit: channel ${e} CONNECTED`),a}catch(t){throw this.#e.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.#e.debug(`rabbit: block reconnect`);return}if(t!==null){if(this.options?.disableReconnect||t?.isConnected())return this.#e.debug(`rabbit: connection - is connected`),t;this.#e.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.#e.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}=L();return c.on(`error`,e=>{this.#e.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.#e.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.#e.debug(`rabbit: connection closed`),this.options?.disableReconnect?(this.#e.error(`${this.DISCONNECT_MSG}${t&&` - ${t}`}`),e.blockReconnect=!0):this.#e.error(`${this.RECONNECT_MSG}${t&&` - ${t}`}`)}),c.once(`connect`,async()=>{this.#e.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.#e.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.#e.debug(`assertQueue->channel.addSetup`,{queueName:t}),await e.addSetup(e=>e.assertQueue(t,o)),this.#e.debug(`assertQueue->channel.assertQueue`,{queueName:t}),r=await e.assertQueue(t,o)}catch(e){if(this.#e.error(`rabbit: assertQueue error`,{queueName:t,options:n,error:e}),this.options?.dontRetryAssert)throw e;{this.#e.debug(`retrying assertQueue`,{queueName:t});let e=await this.assertChannelOld({force:!0,connection:i});await this.deleteQueueOld(t,i),this.#e.debug(`retrying assertQueue->channel.addSetup`,{queueName:t}),await e.addSetup(e=>e.assertQueue(t,o)),this.#e.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.oldPublishChannelSetupPromise)return this.oldPublishChannelSetupPromise;let{promise:n,resolve:r,reject:i}=L();if(this.oldPublishChannelSetupPromise=n,this.oldPublishChannel&&!e)return r(this.oldPublishChannel),n;try{let e=await this.getNewChannelOld({connection:t});e.on(`error`,e=>{this.#e.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.#e.info(`rabbit: deleting queue`,{queue:t});let i=await r.deleteQueue(t);return this.#e.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]=F(n,e),this.oldExchanges[e]=await this.oldAssertExchangePromises[e],this.oldExchanges[e])}async isConnectedOld(){if(this.gracefulShutdownStarted)return!0;this.#e.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.#e.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.#e.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.#e.error(`rabbit: isConnected - false old`,{msg:e.message}),!1}}},X=Y;exports.default=X,exports.sendCeleryTaskViaHttp=W,exports.t=s;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|