UNPKG

@tresdoce-nestjs-toolkit/aws-sqs

Version:

Tresdoce NestJS Toolkit - Módulo de cola de mensajes de AWS Simple Queue Service

477 lines (467 loc) 19.2 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var common = require('@nestjs/common'); var config = require('@nestjs/config'); var clientSqs = require('@aws-sdk/client-sqs'); var core = require('@nestjs/core'); const AWS_SQS_MODULE_OPTIONS = Symbol('AWS_SQS_MODULE_OPTIONS'); const AWS_SQS_QUEUE_PROVIDERS = Symbol('AWS_SQS_QUEUE_PROVIDERS'); const AWS_SQS_MESSAGE_HANDLER = Symbol('AWS_SQS_MESSAGE_HANDLER'); const AWS_SQS_QUEUE = 'AWS_SQS_QUEUE'; var __decorate = undefined && undefined.__decorate || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = undefined && undefined.__metadata || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = undefined && undefined.__param || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); }; }; var AwsSqsService_1; /** * Service to interact with AWS SQS, providing methods to send, receive, and delete messages. */ exports.AwsSqsService = AwsSqsService_1 = class AwsSqsService { /** * Constructor to initialize AWS SQS service with provided options. * @param options Configuration options for AWS SQS. */ constructor(options) { this.options = options; this.logger = new common.Logger(AwsSqsService_1.name); this.sqsClient = new clientSqs.SQSClient(options); } /** * Sends a message to the specified SQS queue. * If the message body is an object, it will be serialized to a string. * @param options Message options to send. * @throws Error if the specified queue is not found. * @example * await this.sqsService.sendMessage({ * queueName: 'orders', * messageBody: { orderId: 123, product: 'Laptop' }, * delaySeconds: 5, * messageAttributes: { * Priority: { DataType: 'String', StringValue: 'High' }, * }, * }); */ async sendMessage(options) { const queue = this.getQueue(options.queueName); const body = this.serializeMessageBody(options.messageBody); const commandInput = { QueueUrl: queue.url, MessageBody: body, DelaySeconds: options.delaySeconds, MessageAttributes: options.messageAttributes, MessageGroupId: options.groupId, MessageDeduplicationId: options.deduplicationId }; await this.sqsClient.send(new clientSqs.SendMessageCommand(commandInput)); this.logger.log(`Message sent to queue "${queue.name}": ${body}`); } /** * Receives messages from the specified SQS queue. * @param queueName Logical name of the queue to receive messages from. * @param maxNumberOfMessages The maximum number of messages to receive (default is 1). * @param waitTimeSeconds The duration (in seconds) for which to wait for a message (default is 20). * @returns An array of messages or an empty array if no messages are found. * @throws Error if the specified queue is not found. * @example * const messages = await this.sqsService.receiveMessage('orders', 5, 10); * if (messages.length > 0) { * messages.forEach(msg => console.log('Received message:', msg.Body)); * } */ async receiveMessage(queueName, maxNumberOfMessages = 1, waitTimeSeconds = 20) { var _response$Messages; const queue = this.getQueue(queueName); const commandInput = { QueueUrl: queue.url, MaxNumberOfMessages: maxNumberOfMessages, WaitTimeSeconds: waitTimeSeconds }; const response = await this.sqsClient.send(new clientSqs.ReceiveMessageCommand(commandInput)); if ((_response$Messages = response.Messages) !== null && _response$Messages !== void 0 && _response$Messages.length) { this.logger.log(`Received ${response.Messages.length} message(s) from queue "${queueName}".`); return response.Messages; } this.logger.log(`No messages available in queue "${queueName}".`); return []; } /** * Deletes a message from the specified SQS queue using its ReceiptHandle. * @param queueName Logical name of the queue. * @param receiptHandle Receipt handle of the message to delete. * @throws Error if the specified queue is not found. * @example * await this.sqsService.deleteMessage('orders', 'AQEBwJnK...'); */ async deleteMessage(queueName, receiptHandle) { const queue = this.getQueue(queueName); const commandInput = { QueueUrl: queue.url, ReceiptHandle: receiptHandle }; await this.sqsClient.send(new clientSqs.DeleteMessageCommand(commandInput)); this.logger.log(`Message deleted from queue "${queueName}".`); } /** * Helper method to get the queue configuration by its logical name. * @param queueName Logical name of the queue. * @returns Queue configuration object. * @throws Error if the queue is not found. */ getQueue(queueName) { const queue = this.options.queues.find(q => q.name === queueName); if (!queue) throw new Error(`Queue "${queueName}" not found`); return queue; } /** * Helper method to serialize the message body to a string. * Validates the body type and serializes it if necessary. * @param body Message body, which can be a string, object, or null. * @returns The message body as a string. * @throws Error if the body is not a valid string or object. */ serializeMessageBody(body) { if (typeof body === 'string') return body; if (typeof body === 'object' && body !== null) return JSON.stringify(body); throw new Error('Message body must be a string or a non-null object'); } }; exports.AwsSqsService = AwsSqsService_1 = __decorate([common.Injectable(), __param(0, common.Inject(AWS_SQS_MODULE_OPTIONS)), __metadata("design:paramtypes", [Object])], exports.AwsSqsService); var __decorate$1 = undefined && undefined.__decorate || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata$1 = undefined && undefined.__metadata || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var AwsSqsListener_1; /** * Listener service that dynamically discovers and binds message handlers * for AWS SQS queues during module initialization. */ let AwsSqsListener = AwsSqsListener_1 = class AwsSqsListener { constructor(sqsService, reflector, modulesContainer) { this.sqsService = sqsService; this.reflector = reflector; this.modulesContainer = modulesContainer; this.logger = new common.Logger(AwsSqsListener_1.name); this.isListening = true; } /** * Called when the module is initialized. * Discovers and registers all message handlers defined in the application. */ async onModuleInit() { const handlers = this.getMessageHandlers(); handlers.forEach(({ queueName, handler }) => this.listenToQueue(queueName, handler)); } /** * Stops the listener when the module is destroyed. */ onModuleDestroy() { this.isListening = false; this.logger.log('SQS Listener stopped.'); } /** * Retrieves all message handlers by scanning the modules for decorated methods. * @returns An array of objects containing the queue name and its associated handler function. * @example * const handlers = listener.getMessageHandlers(); * handlers.forEach(({ queueName, handler }) => * listener.listenToQueue(queueName, handler) * ); */ getMessageHandlers() { const handlers = []; this.modulesContainer.forEach(moduleRef => { Array.from(moduleRef.controllers.values()).forEach(instance => { const prototype = Object.getPrototypeOf(instance.instance); const methodNames = Object.getOwnPropertyNames(prototype); methodNames.forEach(methodName => { const queueName = this.reflector.get(AWS_SQS_MESSAGE_HANDLER, prototype[methodName]); if (queueName) { this.logger.log(`Handler found for queue: ${queueName}`); handlers.push({ queueName, handler: prototype[methodName].bind(instance.instance) }); } }); }); }); return handlers; } /** * Starts listening to a specified SQS queue and continuously processes messages. * If a message is received, the associated handler is executed, * and the message is deleted from the queue upon successful processing. * @param queueName The logical name of the queue to listen to. * @param handler The handler function to process incoming messages. */ async listenToQueue(queueName, handler) { this.logger.log(`Listening to queue: ${queueName}`); try { const isTest = process.env.NODE_ENV === 'test'; let iterationCount = 0; /* istanbul ignore next */ const maxIterations = isTest ? 2 : Infinity; do { const messages = await this.sqsService.receiveMessage(queueName); if (messages.length > 0) { for (const message of messages) { await handler(message); /* istanbul ignore next */ if (message.ReceiptHandle) { await this.sqsService.deleteMessage(queueName, message.ReceiptHandle); this.logger.log(`Message deleted: ${message.MessageId}`); } } } else { this.logger.log(`No messages received from ${queueName}.`); } if (isTest && ++iterationCount >= maxIterations) break; await this.delay(1000); } while (this.isListening); } catch (error) { this.logger.error(`Error on queue ${queueName}: ${error.message}`); } } /** * Helper method to create a delay between iterations. * @param ms Number of milliseconds to delay. * @returns A promise that resolves after the specified delay. */ async delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } }; AwsSqsListener = AwsSqsListener_1 = __decorate$1([common.Injectable(), __metadata$1("design:paramtypes", [exports.AwsSqsService, core.Reflector, core.ModulesContainer])], AwsSqsListener); /** * Factory function to create providers for AWS SQS queues. * This function uses the `AWS_SQS_MODULE_OPTIONS` configuration to dynamically map * each queue into a NestJS provider. The resulting providers can be injected * into other parts of the application by their unique identifiers. * * @returns An array of NestJS providers for the configured SQS queues. * * @example * // AWS SQS Module Configuration * const sqsOptions = { * region: 'us-east-1', * queues: [ * { name: 'orders', url: 'https://sqs.us-east-1.amazonaws.com/123456/orders' }, * { name: 'notifications', url: 'https://sqs.us-east-1.amazonaws.com/123456/notifications' }, * ], * }; * * // Providers created by this function * [ * { provide: 'AWS_SQS_QUEUE_ORDERS', useValue: { name: 'orders', url: 'https://sqs...' } }, * { provide: 'AWS_SQS_QUEUE_NOTIFICATIONS', useValue: { name: 'notifications', url: 'https://sqs...' } }, * ]; * * @example * // Injecting a queue into a service * import { Inject, Injectable } from '@nestjs/common'; * import { QueueConfig } from '../interfaces/aws-sqs.interface'; * * @Injectable() * export class OrdersService { * constructor( * @Inject('AWS_SQS_QUEUE_ORDERS') private readonly ordersQueue: QueueConfig, * ) {} * * getQueueUrl(): string { * return this.ordersQueue.url; * } * } */ const createQueueProviders = () => [{ provide: AWS_SQS_QUEUE_PROVIDERS, /** * Factory function that transforms the list of SQS queues into an array of NestJS providers. * Each queue is provided with a unique identifier based on its name. * * @param options The configuration options for AWS SQS, including the list of queues. * @returns An array of providers, each representing a configured SQS queue. */ useFactory: options => options.queues.map(queue => ({ provide: `${AWS_SQS_QUEUE}_${queue.name.toUpperCase()}`, useValue: queue })), inject: [AWS_SQS_MODULE_OPTIONS] }]; var __decorate$2 = undefined && undefined.__decorate || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var AwsSqsModule_1; /** * AWS SQS Module for NestJS, providing services and listeners to interact with AWS SQS. * This module supports both synchronous and asynchronous registration and configuration. */ exports.AwsSqsModule = AwsSqsModule_1 = class AwsSqsModule { /** * Registers the module with synchronous configuration. * @param options Configuration options for AWS SQS. * @returns A DynamicModule configured with the provided options. * @example * const sqsModule = AwsSqsModule.register({ * region: 'us-east-1', * queues: [{ name: 'orders', url: 'https://sqs.us-east-1.amazonaws.com/123456/orders' }], * }); */ static register(options) { return { module: AwsSqsModule_1, providers: [{ provide: AWS_SQS_MODULE_OPTIONS, useValue: options }, exports.AwsSqsService, AwsSqsListener, ...createQueueProviders()], exports: [exports.AwsSqsService, AWS_SQS_MODULE_OPTIONS, AWS_SQS_QUEUE_PROVIDERS] }; } /** * Registers the module with asynchronous configuration. * @param options Asynchronous configuration options for AWS SQS. * @returns A DynamicModule configured asynchronously. * @example * const sqsModule = AwsSqsModule.registerAsync({ * imports: [ConfigModule], * useFactory: async (configService: ConfigService) => ({ * region: configService.get('AWS_REGION'), * queues: [{ name: 'orders', url: configService.get('ORDERS_QUEUE_URL') }], * }), * inject: [ConfigService], * }); */ static registerAsync(options) { return { module: AwsSqsModule_1, imports: [...(options.imports || [])], providers: [...this.createAsyncProviders(options), ...(options.extraProviders || []), exports.AwsSqsService, AwsSqsListener, ...createQueueProviders()], exports: [exports.AwsSqsService, AWS_SQS_MODULE_OPTIONS, AWS_SQS_QUEUE_PROVIDERS] }; } /** * Registers the module globally with synchronous configuration. * @param options Configuration options for AWS SQS. * @returns A globally configured DynamicModule. */ static forRoot(options) { return { global: true, module: AwsSqsModule_1, providers: [{ provide: AWS_SQS_MODULE_OPTIONS, useValue: options }, exports.AwsSqsService, AwsSqsListener, ...createQueueProviders()], exports: [exports.AwsSqsService, AWS_SQS_MODULE_OPTIONS, AWS_SQS_QUEUE_PROVIDERS] }; } /** * Registers the module globally with asynchronous configuration. * @param options Asynchronous configuration options for AWS SQS. * @returns A globally configured DynamicModule. */ static forRootAsync(options) { return { global: true, module: AwsSqsModule_1, imports: options.imports || [], providers: [...this.createAsyncProviders(options), ...(options.extraProviders || []), exports.AwsSqsService, AwsSqsListener, ...createQueueProviders()], exports: [exports.AwsSqsService, AWS_SQS_MODULE_OPTIONS, AWS_SQS_QUEUE_PROVIDERS] }; } /** * Creates providers for asynchronous registration. * @param options Asynchronous configuration options. * @returns An array of providers. */ static createAsyncProviders(options) { if (options.useExisting || options.useFactory) { return [this.createAsyncOptionsProvider(options)]; } const providers = [this.createAsyncOptionsProvider(options)]; /* istanbul ignore next */ if (options.useClass) providers.push({ provide: options.useClass, useClass: options.useClass }); return providers; } /** * Creates the provider for asynchronous options. * @param options Asynchronous configuration options. * @returns A provider that resolves the module options. */ static createAsyncOptionsProvider(options) { if (options.useFactory) { return { provide: AWS_SQS_MODULE_OPTIONS, useFactory: options.useFactory, inject: options.inject || [] }; } let inject; if (options.useExisting !== undefined) { inject = [options.useExisting]; } if (options.useClass !== undefined && !inject) { inject = [options.useClass]; } return { provide: AWS_SQS_MODULE_OPTIONS, useFactory: async optionsFactory => optionsFactory.createOptions(), inject }; } }; exports.AwsSqsModule = AwsSqsModule_1 = __decorate$2([common.Global(), common.Module({ imports: [config.ConfigModule], providers: [{ provide: AWS_SQS_MODULE_OPTIONS, useFactory: async configService => configService.get('config.sqs'), inject: [config.ConfigService] }, exports.AwsSqsService, AwsSqsListener, ...createQueueProviders()], exports: [exports.AwsSqsService, AWS_SQS_MODULE_OPTIONS, AWS_SQS_QUEUE_PROVIDERS] })], exports.AwsSqsModule); /** * A decorator to mark a method as an AWS SQS message handler for a specific queue. * The decorated method will be invoked whenever a message is received from the specified queue. * * @param queueName The logical name of the queue to handle messages from. * @returns A method decorator that registers the queue name as metadata. * * @example * import { AwsSqsMessageHandler } from './decorators/aws-sqs-message-handler.decorator'; * * class OrdersController { * @AwsSqsMessageHandler('orders') * async handleOrderMessage(message: any): Promise<void> { * console.log(`Processing order message: ${message.Body}`); * } * } */ const AwsSqsMessageHandler = queueName => common.SetMetadata(AWS_SQS_MESSAGE_HANDLER, queueName); exports.AwsSqsMessageHandler = AwsSqsMessageHandler; //# sourceMappingURL=index.js.map