legend-transactional
Version:
A simple transactional, event-driven communication framework for microservices using RabbitMQ
965 lines (924 loc) • 30.5 kB
JavaScript
import amqplib from 'amqplib';
import mitt from 'mitt';
// src/@types/microservices.ts
var availableMicroservices = {
/**
* Test purpose
* Represents the "Image" test microservice.
*/
TestImage: "test-image",
/**
* Test purpose
* Represents the "Mint" test microservice.
*/
TestMint: "test-mint",
/**
* Represents the "auth" microservice.
*/
Auth: "auth",
/**
* Represents the "blockchain" microservice.
*/
Blockchain: "blockchain",
/**
* Represents the "coins" microservice.
*/
Coins: "coins",
/**
* Represents the "legend-missions" microservice.
*/
Missions: "legend-missions",
/**
* Represents the "rankings" microservice.
*/
Rankings: "rankings",
/**
* Represents the "rapid-messaging" microservice.
*/
RapidMessaging: "rapid-messaging",
/**
* Represents the "room-creator" microservice.
*/
RoomCreator: "room-creator",
/**
* Represents the "room-inventory" microservice.
*/
RoomInventory: "room-inventory",
/**
* Represents the "room-snapshot" microservice.
*/
RoomSnapshot: "room-snapshot",
/**
* Represents the "legend-send-email" microservice.
*/
SendEmail: "legend-send-email",
/**
* Represents the "legend-showcase" microservice.
*/
Showcase: "legend-showcase",
/**
* Represents the "social" microservice.
*/
Social: "social",
/**
* Represents the "social-media-rooms" microservice.
*/
SocialMediaRooms: "social-media-rooms",
/**
* Represents the "legend-storage" microservice.
*/
Storage: "legend-storage"
};
// src/@types/event/events.ts
var RoomTypes = {
ISLAND: "island",
HOUSE: "house",
HALL_OF_FAME: "hallOfFame"
};
var PaymentEmailTypes = {
PURCHASE: "purchase",
SUBSCRIPTION: "subscription",
NEW_SUBSCRIPTION: "new_subscription"
};
var gender = {
Male: "MALE",
Female: "FEMALE",
Undefined: "UNDEFINED"
};
var microserviceEvent = {
"TEST.IMAGE": "test.image",
"TEST.MINT": "test.mint",
///////////////////////////
"AUTH.DELETED_USER": "auth.deleted_user",
"AUTH.LOGOUT_USER": "auth.logout_user",
"AUTH.NEW_USER": "auth.new_user",
"AUTH.BLOCKED_USER": "auth.blocked_user",
"COINS.NOTIFY_CLIENT": "coins.notify_client",
"COINS.SEND_EMAIL": "coins.send_email",
"COINS.UPDATE_SUBSCRIPTION": "coins.update_subscription",
"LEGEND_MISSIONS.COMPLETED_MISSION_REWARD": "legend_missions.completed_mission_reward",
"LEGEND_MISSIONS.NEW_MISSION_CREATED": "legend_missions.new_mission_created",
"LEGEND_MISSIONS.ONGOING_MISSION": "legend_missions.ongoing_mission",
"LEGEND_MISSIONS.MISSION_FINISHED": "legend_missions.mission_finished",
"LEGEND_MISSIONS.SEND_EMAIL_CRYPTO_MISSION_COMPLETED": "legend_missions.send_email_crypto_mission_completed",
"LEGEND_MISSIONS.SEND_EMAIL_CODE_EXCHANGE_MISSION_COMPLETED": "legend_missions.send_email_code_exchange_mission_completed",
"LEGEND_MISSIONS.SEND_EMAIL_NFT_MISSION_COMPLETED": "legend_missions.send_email_nft_mission_completed",
"LEGEND_RANKINGS.RANKINGS_FINISHED": "legend_rankings.rankings_finished",
"LEGEND_RANKINGS.NEW_RANKING_CREATED": "legend_rankings.new_ranking_created",
"LEGEND_RANKINGS.INTERMEDIATE_REWARD": "legend_rankings.intermediate_reward",
"LEGEND_SHOWCASE.PRODUCT_VIRTUAL_DELETED": "legend_showcase.product_virtual_deleted",
"LEGEND_SHOWCASE.UPDATE_ALLOWED_MISSION_SUBSCRIPTION_IDS": "legend_showcase.update_allowed_mission_subscription_ids",
"LEGEND_SHOWCASE.UPDATE_ALLOWED_RANKING_SUBSCRIPTION_IDS": "legend_showcase.update_allowed_ranking_subscription_ids",
"ROOM_CREATOR.CREATED_ROOM": "room_creator.created_room",
"ROOM_CREATOR.UPDATED_ROOM": "room_creator.updated_room",
"ROOM_INVENTORY.UPDATE_VP_BUILDING_IMAGE": "room_inventory.update_vp_building_image",
"ROOM_SNAPSHOT.BUILDING_CHANGE_IN_ISLAND": "room_snapshot.building_change_in_island",
"ROOM_SNAPSHOT.FIRST_SNAPSHOT": "room_snapshot.first_snapshot",
"SOCIAL.BLOCK_CHAT": "social.block_chat",
"SOCIAL.NEW_USER": "social.new_user",
"SOCIAL.UNBLOCK_CHAT": "social.unblock_chat",
"SOCIAL.UPDATED_USER": "social.updated_user",
"SOCIAL_MEDIA_ROOMS.DELETE_IN_BATCH": "social_media_rooms.delete_in_batch"
};
// src/@types/rabbit-mq.ts
var queue = {
/**
* Queue used for sending replies in response to saga events.
*/
ReplyToSaga: "reply_to_saga",
/**
* Queue used for commencing a saga.
*/
CommenceSaga: "commence_saga"
};
var exchange = {
/**
* Exchange dedicated to requeueing messages that require further processing in a saga process
*/
Requeue: "requeue_exchange",
/**
* Exchange for sending command messages to various consumers in a saga process
*/
Commands: "commands_exchange",
/**
* Exchange used for replying to saga events from consumers.
*/
ReplyToSaga: "reply_exchange",
/**
* Exchange used for starting a saga.
*/
CommenceSaga: "commence_saga_exchange",
/**
* Exchange used for starting a saga.
*/
Matching: "matching_exchange",
/**
* Exchange dedicated to requeueing messages that require further processing.
*/
MatchingRequeue: "matching_requeue_exchange"
};
// src/@types/saga/commands/auth.ts
var authCommands = {
/**
* Command to create a new user.
*/
CreateUser: "create_user"
};
// src/@types/saga/commands/blockchain.ts
var blockchainCommands = {
/**
* Saga step to transfer crypto reward to the winner of a mission.
*/
TransferMissionRewardToWinner: "crypto_reward:transfer_mission_reward_to_winner",
/**
* Saga step to make transfers to winners.
*/
TransferRewardToWinners: "crypto_reward:transfer_reward_to_winners"
};
// src/@types/saga/commands/image.ts
var testImageCommands = {
/**
* Command to create an image.
*/
CreateImage: "create_image",
/**
* Command to update a token for an image.
*/
UpdateToken: "update_token"
};
// src/@types/saga/commands/mint.ts
var testMintCommands = {
/**
* Command to mint an image.
*/
MintImage: "mint_image"
};
// src/@types/saga/commands/coins.ts
var coinsCommands = {
/**
* Saga step to deduct coins when a user purchase a resource
*/
DeductCoins: "resource_purchased:deduct_coins",
/**
* Saga step to reward coins to users based on their rankings
*/
RankingsRewardCoins: "rankings_users_reward:reward_coins"
};
// src/@types/saga/commands/rankings.ts
var rankingsCommands = {};
// src/@types/saga/commands/rapid-messaging.ts
var rapidMessagingCommands = {};
// src/@types/saga/commands/room-inventory.ts
var roomInventoryCommands = {
/**
* Command to save a purchased resource on user inventory
*/
SavePurchasedResource: "resource_purchased:save_purchased_resource"
};
// src/@types/saga/commands/room-snapshot.ts
var roomSnapshotCommands = {};
// src/@types/saga/commands/roomCreator.ts
var roomCreatorCommands = {
/**
* Command to update the island room template.
*/
update_island_room_template: "update_island_room_template"
};
// src/@types/saga/commands/send-email.ts
var sendEmailCommands = {};
// src/@types/saga/commands/showcase.ts
var showcaseCommands = {
/**
* Command to update the product virtual and randomize the pv-image related.
*/
randomize_island_pv_image: "randomize_island_pv_image"
};
// src/@types/saga/commands/social.ts
var socialCommands = {
/**
* Command to create a new social user.
*/
CreateSocialUser: "create_social_user",
/**
* Command to update the social user's image.
*/
UpdateUserImage: "update_user:image"
};
// src/@types/saga/commands/social-media-rooms.ts
var socialMediaRoomsCommands = {};
// src/@types/saga/commands/storage.ts
var storageCommands = {
/**
* Command to store a file from base64.
*/
UploadFile: "upload_file"
};
// src/@types/saga/commence.ts
var sagaTitle = {
/**
* Saga used in the flow to purchase resources and deduct coins from the user.
*/
PurchaseResourceFlow: "purchase_resource_flow",
/**
* Saga used in to reward users based on their rankings.
*/
RankingsUsersReward: "rankings_users_reward",
/**
* Saga used to initiate a crypto transfer for a mission winner.
*/
TransferCryptoRewardToMissionWinner: "transfer_crypto_reward_to_mission_winner",
/**
* Saga used to initiate a crypto transfer for ranking winners.
*/
TransferCryptoRewardToRankingWinners: "transfer_crypto_reward_to_ranking_winners"
};
// src/@types/saga/sagaStep.ts
var status = {
/**
* The step is pending and hasn't been processed yet.
*/
Pending: "pending",
/**
* The step has been successfully executed.
*/
Success: "success",
/**
* The step execution has failed.
*/
Failure: "failure",
/**
* The step has been sent but not yet executed.
*/
Sent: "sent"
};
var conn = null;
var url = null;
var isTheConnectionClosed = true;
var saveUri = (uri) => {
if (!url) {
url = uri;
}
};
var getUri = () => {
if (url === null) {
throw new Error("RabbitMQ URI not initialized.");
}
return url;
};
var startListeners = (c) => {
c.addListener("close", (e) => {
isTheConnectionClosed = true;
console.error("[legend_transac:__Connection closed__]", e.message);
});
c.addListener("error", (e) => {
isTheConnectionClosed = true;
console.error("[legend_transac:__Connection error__]", e.message);
});
};
var getRabbitMQConn = async () => {
if (conn === null) {
conn = await amqplib.connect(getUri());
isTheConnectionClosed = false;
startListeners(conn);
}
return conn;
};
var closeRabbitMQConn = async () => {
if (conn !== null) {
await conn.close();
conn = null;
url = null;
}
};
var healthCheckQueue = null;
var saveQueueForHealthCheck = (queue2) => {
healthCheckQueue = queue2;
};
var isConnectionHealthy = async () => {
let isHealthy = false;
if (isTheConnectionClosed) return isHealthy;
if (conn === null) return isHealthy;
if (healthCheckQueue === null) return isHealthy;
const queue2 = healthCheckQueue;
const closeListener = (e) => {
isTheConnectionClosed = true;
console.error("[legend_transac:health_check_listener:__Connection closed__]", e.message);
};
const errorListener = (e) => {
isTheConnectionClosed = true;
console.error("[legend_transac:health_check_listener:__Connection error__]", e.message);
};
conn.addListener("close", closeListener);
conn.addListener("error", errorListener);
const testChannel = await conn.createConfirmChannel();
try {
const testChannelPromise = new Promise((resolve, reject) => {
testChannel.checkQueue(queue2).then(() => {
isHealthy = true;
resolve();
}).catch((e) => {
console.error("[legend_transac:health_check_listener:Check failed]", e.message);
reject(e);
});
});
await testChannelPromise;
} catch (e) {
return isHealthy;
}
await testChannel.close();
conn.removeListener("close", closeListener);
conn.removeListener("error", errorListener);
return isHealthy;
};
// src/Connections/consumeChannel.ts
var consumeChannel = null;
var getConsumeChannel = async () => {
if (consumeChannel === null) {
consumeChannel = await (await getRabbitMQConn()).createChannel();
}
return consumeChannel;
};
var closeConsumeChannel = async () => {
if (consumeChannel !== null) {
await consumeChannel.close();
consumeChannel = null;
}
};
// src/constants.ts
var NACKING_DELAY_MS = 2e3;
var MAX_OCCURRENCE = 19;
var MAX_NACK_RETRIES = 20;
var nodeDataDefaults = {
payload: {},
previousPayload: {},
status: status.Pending,
isCurrentStep: false
};
// src/utils/fibonacci.ts
var fibonacci = (n) => {
if (n <= 0) return 0;
if (n === 1) return 1;
let fibPrev = 0;
let fibCurrent = 1;
for (let i = 2; i <= n; i++) {
const temp = fibCurrent;
fibCurrent += fibPrev;
fibPrev = temp;
}
return fibCurrent;
};
// src/utils/getters.ts
var getQueueName = (microservice) => {
return `${microservice}_saga_commands`;
};
var getQueueConsumer = (microservice) => {
return {
queueName: getQueueName(microservice),
exchange: exchange.Commands
};
};
var getEventKey = (event) => {
return event.toUpperCase();
};
var getEventObject = (event) => {
const key = getEventKey(event);
return {
[key]: event
};
};
// src/Consumer/channels/Consume.ts
var ConsumeChannel = class {
/**
* The AMQP Channel object used for communication with RabbitMQ.
*/
channel;
/**
* The message received from RabbitMQ that this channel is currently processing.
*/
msg;
/**
* The name of the queue from which the message was consumed.
*/
queueName;
/**
* Creates a new `ConsumeChannel` instance.
*
* @param channel - The AMQP Channel for interacting with RabbitMQ.
* @param msg - The consumed message.
* @param queueName - The name of the source queue.
*/
constructor(channel, msg, queueName) {
this.channel = channel;
this.msg = msg;
this.queueName = queueName;
}
/**
* Negatively acknowledges (NACKs) the message with a specified delay and maximum retry count.
*
* This method is useful when you want to requeue the message for later processing, especially if the current attempt failed due to a temporary issue.
*
* @param delay - The delay (in milliseconds) before requeueing the message. Defaults to `NACKING_DELAY_MS`.
* @param maxRetries - The maximum number of times to requeue the message before giving up. Defaults to `undefined`, never giving up.
* @returns An object containing:
* - `count`: The current retry count.
* - `delay`: The actual delay applied to the nack.
*
* @see NACKING_DELAY_MS
*/
nackWithDelay = (delay = NACKING_DELAY_MS, maxRetries) => {
const { delay: delayNackRetry, count } = this.nack({ delay, maxRetries });
return { count, delay: delayNackRetry };
};
/**
* Negatively acknowledges (NACKs) the message using a Fibonacci backoff strategy.
*
* The delay before requeuing increases with each retry according to the Fibonacci sequence, helping to avoid overwhelming the system in case of repeated failures.
*
* @param maxOccurrence - The maximum number of times the Fibonacci delay is allowed to increase before being reset. Defaults to `MAX_OCCURRENCE`.
* @param maxRetries - The maximum number of times to requeue the message before giving up. Defaults to `undefined`, never giving up.
* @returns An object containing:
* - `count`: The current retry count.
* - `delay`: The calculated Fibonacci delay (in milliseconds) applied to the nack.
* - `occurrence`: The current occurrence count for the Fibonacci sequence.
* @see MAX_OCCURRENCE
*/
nackWithFibonacciStrategy = (maxOccurrence = MAX_OCCURRENCE, maxRetries) => {
return this.nack({ maxOccurrence, maxRetries });
};
/**
* Private helper function to handle the actual NACK logic.
*
* This method performs the NACK operation, manages retry counts and delays, and republishes the message for requeuing with appropriate headers and routing.
*
* @param nackOptions - An object specifying either:
* - `delay` and `maxRetries`: For linear backoff with a fixed delay and retry limit.
* - `maxOccurrence`: For Fibonacci backoff with a maximum occurrence count.
*/
nack = ({
maxRetries,
maxOccurrence,
delay
}) => {
const { msg, queueName, channel } = this;
channel.nack(msg, false, false);
let count = 0;
if (msg.properties.headers && msg.properties.headers["x-retry-count"]) {
count = Number(msg.properties.headers["x-retry-count"]);
}
count++;
let occurrence = 0;
if (msg.properties.headers && msg.properties.headers["x-occurrence"]) {
occurrence = Number(msg.properties.headers["x-occurrence"]);
if (occurrence >= (maxOccurrence ?? Infinity)) {
occurrence = 0;
}
}
occurrence++;
const nackDelay = delay ?? fibonacci(occurrence) * 1e3;
if (maxRetries && count > maxRetries) {
console.error(`MAX NACK RETRIES REACHED: ${maxRetries} - NACKING ${queueName} - ${msg.content.toString()}`);
return { count, delay: nackDelay, occurrence };
}
if (msg.properties?.headers?.["x-death"] && msg.properties.headers["x-death"].length > 1) {
const logData = {
"x-death": msg.properties.headers["x-death"],
queueName,
msg: msg.content.toString(),
headers: msg.properties.headers
};
console.warn("x-death length > 1 -> TIME TO REFACTOR", logData);
}
const xHeaders = {
"x-retry-count": count,
// 'count' and 'occurrence' are the same if the strategy is delay
"x-occurrence": occurrence
};
if (msg.fields.exchange === exchange.Matching) {
if (msg.properties?.headers?.["all-micro"]) {
delete msg.properties.headers["all-micro"];
}
channel.publish(exchange.MatchingRequeue, ``, msg.content, {
expiration: nackDelay,
headers: {
...msg.properties.headers,
// el nacking es dirigido a un microservicio en particular, el que nackeó.
micro: queueName,
...xHeaders
},
persistent: true
});
} else {
channel.publish(exchange.Requeue, `${queueName}_routing_key`, msg.content, {
expiration: nackDelay,
headers: { ...msg.properties.headers, ...xHeaders },
persistent: true
});
}
return { count, delay: nackDelay, occurrence };
};
};
var Consume_default = ConsumeChannel;
// src/Consumer/channels/CommenceSaga.ts
var SagaCommenceConsumeChannel = class extends Consume_default {
/**
* Method to acknowledge the message.
*/
ackMessage() {
this.channel.ack(this.msg, false);
}
};
// src/Consumer/callbacks/commenceSaga.ts
var commenceSagaConsumeCallback = (msg, channel, e, queueName) => {
if (!msg) {
console.error("NO MSG AVAILABLE");
return;
}
let saga;
try {
saga = JSON.parse(msg.content.toString());
} catch (error) {
console.error("ERROR PARSING MSG", error);
channel.nack(msg, false, false);
return;
}
const responseChannel = new SagaCommenceConsumeChannel(channel, msg, queueName);
e.emit(saga.title, { saga, channel: responseChannel });
};
// src/Consumer/channels/Events.ts
var EventsConsumeChannel = class extends Consume_default {
/**
* Acknowledges the consumed saga event/command.
*/
ackMessage() {
this.channel.ack(this.msg, false);
}
};
// src/Consumer/callbacks/event.ts
var eventCallback = (msg, channel, e, queueName) => {
if (!msg) {
console.error("mgs not AVAILABLE");
return;
}
const stringPayload = msg.content.toString();
let payload;
try {
payload = JSON.parse(stringPayload);
} catch (error) {
console.error("ERROR PARSING MSG", error);
channel.nack(msg, false, false);
return;
}
const headers = msg.properties.headers;
if (!headers || Object.values(headers).length === 0) {
console.error("headers not AVAILABLE, is a headers exchange");
channel.nack(msg, false, false);
return;
}
const allValues = Object.values(headers);
const event = [];
for (const value of allValues) {
if (typeof value === "string" && Object.values(microserviceEvent).includes(value)) {
event.push(value);
}
}
if (event.length === 0) {
console.error("Invalid header value", headers);
channel.nack(msg, false, false);
return;
}
if (event.length > 1) {
console.error(
"More then one valid header, using the first one detected, that is because the payload is typed with a particular event",
{ headersReceived: headers, eventsDetected: event }
);
}
const responseChannel = new EventsConsumeChannel(channel, msg, queueName);
e.emit(event[0], { payload, channel: responseChannel });
};
// src/Consumer/channels/Step.ts
var MicroserviceConsumeChannel = class extends Consume_default {
/**
* The saga step associated with the consumed message.
*/
step;
/**
* Constructs a new instance of the ConsumeChannel class.
*
* @param {Channel} channel - The channel to interact with the message broker.
* @param {ConsumeMessage} msg - The consumed message to be processed.
* @param {string} queueName - The name of the queue from which the message was consumed.
* @param {SagaStep} step - The saga step associated with the consumed message.
*/
constructor(channel, msg, queueName, step) {
super(channel, msg, queueName);
this.step = step;
}
ackMessage(payloadForNextStep = {}) {
this.step.status = status.Success;
const previousPayload = this.step.previousPayload;
let metaData = {};
if (previousPayload) {
metaData = Object.keys(previousPayload).filter((key) => key.startsWith("__")).reduce((obj, key) => (obj[key] = previousPayload[key], obj), {});
}
this.step.payload = {
...payloadForNextStep,
...metaData
};
sendToQueue(queue.ReplyToSaga, this.step).then(() => {
this.channel.ack(this.msg, false);
}).catch((err) => {
console.error(err);
});
}
};
var SagaConsumeChannel = class extends MicroserviceConsumeChannel {
/**
* Acknowledges the consumed saga event/command.
*/
ackMessage() {
this.channel.ack(this.msg, false);
}
};
// src/Consumer/callbacks/saga.ts
var sagaConsumeCallback = (msg, channel, e, queueName) => {
if (!msg) {
console.error("NO MSG AVAILABLE");
return;
}
let currentStep;
try {
currentStep = JSON.parse(msg.content.toString());
} catch (error) {
console.error("ERROR PARSING MSG", error);
channel.nack(msg, false, false);
return;
}
const responseChannel = new SagaConsumeChannel(channel, msg, queueName, currentStep);
e.emit(currentStep.command, { step: currentStep, channel: responseChannel });
};
// src/Consumer/callbacks/sagaStep.ts
var sagaStepCallback = (msg, channel, e, queueName) => {
if (!msg) {
console.error("NO MSG AVAILABLE");
return;
}
let currentStep;
try {
currentStep = JSON.parse(msg.content.toString());
} catch (error) {
console.error("ERROR PARSING MSG", error);
channel.nack(msg, false, false);
return;
}
const { command, sagaId, previousPayload } = currentStep;
const responseChannel = new MicroserviceConsumeChannel(channel, msg, queueName, currentStep);
e.emit(command, { sagaId, payload: previousPayload, channel: responseChannel });
};
// src/Consumer/consume.ts
var consume = async (e, queueName, cb) => {
const channel = await getConsumeChannel();
await channel.prefetch(1);
await channel.consume(
queueName,
(msg) => {
cb(msg, channel, e, queueName);
},
{
exclusive: false,
// noAck means that the message will be acknowledged automatically by the broker once it is delivered.
noAck: false
}
);
};
// src/Consumer/create.ts
var createConsumers = async (consumers) => {
const channel = await getConsumeChannel();
for await (const consumer of consumers) {
const { exchange: consumerExchange, queueName } = consumer;
const requeueQueue = `${queueName}_requeue`;
const routingKey = `${queueName}_routing_key`;
await channel.assertExchange(consumerExchange, "direct", { durable: true });
await channel.assertQueue(queueName, { durable: true });
await channel.bindQueue(queueName, consumerExchange, routingKey);
saveQueueForHealthCheck(queueName);
await channel.assertExchange(exchange.Requeue, "direct", { durable: true });
await channel.assertQueue(requeueQueue, {
durable: true,
arguments: { "x-dead-letter-exchange": consumerExchange }
});
await channel.bindQueue(requeueQueue, exchange.Requeue, routingKey);
}
};
// src/Consumer/header.ts
var createHeaderConsumers = async (queueName, events) => {
const channel = await getConsumeChannel();
const requeueQueue = `${queueName}_matching_requeue`;
await channel.assertExchange(exchange.Matching, "headers", { durable: true });
await channel.assertExchange(exchange.MatchingRequeue, "headers", { durable: true });
await channel.assertQueue(queueName, { durable: true });
saveQueueForHealthCheck(queueName);
await channel.assertQueue(requeueQueue, {
durable: true,
arguments: { "x-dead-letter-exchange": exchange.Matching }
});
for (const ev of Object.values(microserviceEvent)) {
const headerEvent = getEventObject(ev);
await channel.assertExchange(ev, "headers", { durable: true });
await channel.bindExchange(ev, exchange.Matching, "", {
...headerEvent,
// key para emitir eventos a todos lo micros, todos los micros tienen el bind a este exchange "ev"
"all-micro": "yes",
"x-match": "all"
// se tienen que cumplir todos los argumentos
});
await channel.assertExchange(`${ev}_requeue`, "headers", { durable: true });
await channel.bindExchange(`${ev}_requeue`, exchange.MatchingRequeue, "", headerEvent);
const headersArgs = {
...headerEvent,
micro: queueName,
"x-match": "all"
};
if (events.includes(ev)) {
await channel.bindQueue(queueName, ev, "", headerEvent);
await channel.bindQueue(requeueQueue, `${ev}_requeue`, "", headersArgs);
await channel.assertExchange(`${ev}_${queueName}`, "headers", {
durable: true
});
await channel.bindExchange(`${ev}_${queueName}`, exchange.Matching, "", headersArgs);
await channel.bindQueue(queueName, `${ev}_${queueName}`, "", headersArgs);
} else {
await channel.unbindQueue(queueName, ev, "", headerEvent);
await channel.unbindQueue(requeueQueue, `${ev}_requeue`, "", headersArgs);
await channel.deleteExchange(`${ev}_${queueName}`, { ifUnused: false });
}
}
};
var isReady = false;
var prepare = async (url2) => {
if (isReady) return;
saveUri(url2);
await getRabbitMQConn();
await getConsumeChannel();
isReady = true;
};
var startGlobalSagaStepListener = async (url2) => {
await prepare(url2);
const queueO = {
queueName: queue.ReplyToSaga,
exchange: exchange.ReplyToSaga
};
const e = mitt();
await createConsumers([queueO]);
void consume(e, queueO.queueName, sagaConsumeCallback);
return e;
};
var commenceSagaListener = async (url2) => {
await prepare(url2);
const q = {
queueName: queue.CommenceSaga,
exchange: exchange.CommenceSaga
};
const e = mitt();
await createConsumers([q]);
void consume(e, q.queueName, commenceSagaConsumeCallback);
return e;
};
var transactionalInitialized = false;
var Transactional = class {
constructor(url2) {
this.url = url2;
if (transactionalInitialized) {
throw new Error("Transactional already initialized");
}
transactionalInitialized = true;
}
startGlobalSagaStepListener = () => {
return startGlobalSagaStepListener(this.url);
};
commenceSagaListener = () => {
return commenceSagaListener(this.url);
};
};
var connectToSagaCommandEmitter = async (config) => {
await prepare(config.url);
const q = getQueueConsumer(config.microservice);
const e = mitt();
await createConsumers([q]);
void consume(e, q.queueName, sagaStepCallback);
return e;
};
var connectToEvents = async (config) => {
await prepare(config.url);
const queueName = `${config.microservice}_match_commands`;
const e = mitt();
await createHeaderConsumers(queueName, config.events);
void consume(e, queueName, eventCallback);
return e;
};
var sagaInitialized = false;
var Saga = class {
constructor(conf) {
this.conf = conf;
if (sagaInitialized) {
throw new Error("Saga already initialized");
}
sagaInitialized = true;
}
connectToEvents = () => {
return connectToEvents(this.conf);
};
connectToSagaCommandEmitter = () => {
return connectToSagaCommandEmitter(this.conf);
};
};
// src/Connections/stop.ts
var stopRabbitMQ = async () => {
await closeConsumeChannel();
await closeSendChannel();
await closeRabbitMQConn();
};
// src/Broker/sendChannel.ts
var sendChannel = null;
var getSendChannel = async () => {
if (sendChannel === null) {
sendChannel = await (await getRabbitMQConn()).createChannel();
}
return sendChannel;
};
var closeSendChannel = async () => {
if (sendChannel !== null) {
await sendChannel.close();
sendChannel = null;
}
};
// src/Broker/SendToQueue.ts
var sendToQueue = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async (queueName, payload) => {
const channel = await getSendChannel();
await channel.assertQueue(queueName, { durable: true });
channel.sendToQueue(queueName, Buffer.from(JSON.stringify(payload)), {
persistent: true
});
}
);
var commenceSaga = async (sagaTitle2, payload) => {
const saga = {
title: sagaTitle2,
payload
};
await sendToQueue(queue.CommenceSaga, saga);
};
// src/Broker/PublishToExchange.ts
var publishEvent = async (msg, event) => {
const channel = await getSendChannel();
await channel.assertExchange(exchange.Matching, "headers", { durable: true });
channel.publish(exchange.Matching, ``, Buffer.from(JSON.stringify(msg)), {
headers: {
...getEventObject(event),
// key para emitir eventos a todos los micros, todos los micros tienen el bind al exchange Matching
"all-micro": "yes"
}
});
};
export { EventsConsumeChannel, MAX_NACK_RETRIES, MAX_OCCURRENCE, MicroserviceConsumeChannel, NACKING_DELAY_MS, PaymentEmailTypes, RoomTypes, Saga, SagaCommenceConsumeChannel, SagaConsumeChannel, Transactional, authCommands, availableMicroservices, blockchainCommands, closeConsumeChannel, closeRabbitMQConn, closeSendChannel, coinsCommands, commenceSaga, commenceSagaConsumeCallback, commenceSagaListener, connectToEvents, connectToSagaCommandEmitter, consume, createConsumers, createHeaderConsumers, eventCallback, exchange, fibonacci, gender, getConsumeChannel, getEventKey, getEventObject, getQueueConsumer, getQueueName, getRabbitMQConn, getSendChannel, isConnectionHealthy, microserviceEvent, nodeDataDefaults, publishEvent, queue, rankingsCommands, rapidMessagingCommands, roomCreatorCommands, roomInventoryCommands, roomSnapshotCommands, sagaConsumeCallback, sagaStepCallback, sagaTitle, saveQueueForHealthCheck, saveUri, sendEmailCommands, sendToQueue, showcaseCommands, socialCommands, socialMediaRoomsCommands, startGlobalSagaStepListener, status, stopRabbitMQ, storageCommands, testImageCommands, testMintCommands };