UNPKG

legend-transactional

Version:

A simple transactional, event-driven communication framework for microservices using RabbitMQ

965 lines (924 loc) 30.5 kB
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 };