├── index.d.ts ├── index.ts ├── lib ├── helpers │ ├── index.ts │ └── queueProviders.helper.ts ├── constants │ ├── index.ts │ └── providerTokens.constant.ts ├── interfaces │ ├── index.ts │ └── queueProcessor.interface.ts ├── decorators │ ├── index.ts │ ├── injectChannel.decorator.ts │ └── queueProcessor.decorator.ts ├── types │ ├── index.ts │ ├── queueProcessorOptions.type.ts │ └── carrotModuleOptions.types.ts ├── index.ts ├── carrot.module.ts ├── carrot.service.ts └── processor.explorer.ts ├── .prettierrc ├── static └── banner.png ├── tsconfig.json ├── index.js ├── .gitignore ├── eslint.config.mjs ├── LICENSE.md ├── package.json └── README.md /index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist'; 2 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | export * from './dist'; 2 | -------------------------------------------------------------------------------- /lib/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './queueProviders.helper'; 2 | -------------------------------------------------------------------------------- /lib/constants/index.ts: -------------------------------------------------------------------------------- 1 | export * from './providerTokens.constant'; 2 | -------------------------------------------------------------------------------- /lib/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from './queueProcessor.interface'; 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /static/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shayan-shojaei/nestjs-carrot/HEAD/static/banner.png -------------------------------------------------------------------------------- /lib/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export * from './injectChannel.decorator'; 2 | export * from './queueProcessor.decorator'; 3 | -------------------------------------------------------------------------------- /lib/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './queueProcessorOptions.type'; 2 | export * from './carrotModuleOptions.types'; 3 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './carrot.module'; 2 | export * from './decorators'; 3 | export * from './interfaces'; 4 | export * from './helpers'; 5 | -------------------------------------------------------------------------------- /lib/interfaces/queueProcessor.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IQueueProcessor { 2 | process(data: Buffer | string | unknown): Promise | void; 3 | } 4 | -------------------------------------------------------------------------------- /lib/constants/providerTokens.constant.ts: -------------------------------------------------------------------------------- 1 | export const CARROT_MODULE_OPTIONS = Symbol('CARROT_MODULE_OPTIONS'); 2 | 3 | export const CARROT_CHANNEL = Symbol('CARROT_CHANNEL'); 4 | -------------------------------------------------------------------------------- /lib/decorators/injectChannel.decorator.ts: -------------------------------------------------------------------------------- 1 | import { Inject } from '@nestjs/common'; 2 | import { CARROT_CHANNEL } from '../constants'; 3 | 4 | export const InjectChannel = () => Inject(CARROT_CHANNEL); 5 | -------------------------------------------------------------------------------- /lib/types/queueProcessorOptions.type.ts: -------------------------------------------------------------------------------- 1 | export type QueueProcessorPayloadType = 'json' | 'buffer' | 'string'; 2 | 3 | /** 4 | * Options for the queue processor. 5 | * 6 | * @param type The payload type to convert the message to. Default is JSON. 7 | */ 8 | export type QueueProcessorOptions = { 9 | type?: QueueProcessorPayloadType; 10 | }; 11 | -------------------------------------------------------------------------------- /lib/helpers/queueProviders.helper.ts: -------------------------------------------------------------------------------- 1 | import { Provider } from '@nestjs/common'; 2 | import { CarrotService } from '../carrot.service'; 3 | import { CARROT_CHANNEL } from '../constants'; 4 | 5 | export class QueueProvidersHelper { 6 | static createProvider(queueNames: string[]): Provider { 7 | return { 8 | provide: CARROT_CHANNEL, 9 | useFactory: (carrotService: CarrotService) => { 10 | return carrotService.getPublisherChannel(queueNames); 11 | }, 12 | inject: [CarrotService], 13 | }; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/types/carrotModuleOptions.types.ts: -------------------------------------------------------------------------------- 1 | import { AmqpConnectionManagerOptions } from 'amqp-connection-manager'; 2 | 3 | export type CarrotModuleOptions = { 4 | /** 5 | * amqp connection url(s) 6 | * 7 | * @example ["amqp://127.0.0.1:5672"] 8 | */ 9 | url: string[]; 10 | 11 | /** 12 | * TODO: add queue prefix feature 13 | * 14 | * queue prefix 15 | * 16 | * Prefixes all queue names with the given string. 17 | * 18 | * @example myapp. 19 | */ 20 | // queuePrefix?: string; 21 | 22 | /** 23 | * connection options 24 | */ 25 | connectionOptions?: AmqpConnectionManagerOptions; 26 | 27 | /** 28 | * global flag 29 | * 30 | * If set to true, the module will be registered as a global module. 31 | */ 32 | global?: boolean; 33 | }; 34 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": false, 6 | "noLib": false, 7 | "emitDecoratorMetadata": true, 8 | "esModuleInterop": true, 9 | "experimentalDecorators": true, 10 | "target": "ES2021", 11 | "sourceMap": false, 12 | "allowSyntheticDefaultImports": true, 13 | "outDir": "./dist", 14 | "rootDir": "./lib", 15 | "skipLibCheck": true, 16 | "strictNullChecks": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "noImplicitAny": false, 19 | "strictBindCallApply": false, 20 | "noFallthroughCasesInSwitch": false 21 | }, 22 | "include": [ 23 | "lib/**/*" 24 | ], 25 | "exclude": [ 26 | "node_modules", 27 | "**/*.spec.ts", 28 | "tests" 29 | ] 30 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function() { return m[k]; } }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | }) : (function(o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | })); 13 | var __exportStar = (this && this.__exportStar) || function(m, exports) { 14 | for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); 15 | }; 16 | Object.defineProperty(exports, "__esModule", { value: true }); 17 | __exportStar(require("./dist"), exports); 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | /build 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | pnpm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # OS 16 | .DS_Store 17 | 18 | # Tests 19 | /coverage 20 | /.nyc_output 21 | 22 | # IDEs and editors 23 | /.idea 24 | .project 25 | .classpath 26 | .c9/ 27 | *.launch 28 | .settings/ 29 | *.sublime-workspace 30 | 31 | # IDE - VSCode 32 | .vscode/* 33 | !.vscode/settings.json 34 | !.vscode/tasks.json 35 | !.vscode/launch.json 36 | !.vscode/extensions.json 37 | 38 | # dotenv environment variable files 39 | .env 40 | .env.development.local 41 | .env.test.local 42 | .env.production.local 43 | .env.local 44 | 45 | # temp directory 46 | .temp 47 | .tmp 48 | 49 | # Runtime data 50 | pids 51 | *.pid 52 | *.seed 53 | *.pid.lock 54 | 55 | # Diagnostic reports (https://nodejs.org/api/report.html) 56 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 57 | 58 | tsconfig.tsbuildinfo -------------------------------------------------------------------------------- /lib/decorators/queueProcessor.decorator.ts: -------------------------------------------------------------------------------- 1 | import { QueueProcessorOptions } from '../types'; 2 | 3 | export const QUEUE_PROCESSOR_METADATA = Symbol('QUEUE_PROCESSOR_METADATA'); 4 | export const QUEUE_PROCESSOR_PAYLOAD_TYPE_METADATA = Symbol( 5 | 'QUEUE_PROCESSOR_PAYLOAD_TYPE_METADATA', 6 | ); 7 | 8 | /** 9 | * Decorator to mark a class as a queue processor. 10 | * 11 | * @param queue The queue name to listen to. 12 | * @param options {QueueProcessorOptions} Additional options. 13 | * @param options.type The payload type to convert the message to. Default is JSON. 14 | * @constructor 15 | */ 16 | export const QueueProcessor = ( 17 | queue: string, 18 | options?: QueueProcessorOptions, 19 | ): ClassDecorator => { 20 | return (target: object) => { 21 | Reflect.defineMetadata(QUEUE_PROCESSOR_METADATA, queue, target); 22 | Reflect.defineMetadata( 23 | QUEUE_PROCESSOR_PAYLOAD_TYPE_METADATA, 24 | options?.type || 'json', 25 | target, 26 | ); 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import eslint from '@eslint/js'; 3 | import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; 4 | import globals from 'globals'; 5 | import tseslint from 'typescript-eslint'; 6 | 7 | export default tseslint.config( 8 | { 9 | ignores: ['eslint.config.mjs', 'index.*'], 10 | }, 11 | eslint.configs.recommended, 12 | ...tseslint.configs.recommended, 13 | eslintPluginPrettierRecommended, 14 | { 15 | languageOptions: { 16 | globals: { 17 | ...globals.node, 18 | ...globals.jest, 19 | }, 20 | ecmaVersion: 5, 21 | sourceType: 'module', 22 | parserOptions: { 23 | projectService: true, 24 | tsconfigRootDir: import.meta.dirname, 25 | }, 26 | }, 27 | }, 28 | { 29 | rules: { 30 | '@typescript-eslint/no-explicit-any': 'off', 31 | '@typescript-eslint/no-floating-promises': 'warn', 32 | '@typescript-eslint/no-unsafe-argument': 'off' 33 | }, 34 | }, 35 | ); -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Shayan Shojaei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/carrot.module.ts: -------------------------------------------------------------------------------- 1 | import { DynamicModule, Module } from '@nestjs/common'; 2 | import { CarrotService } from './carrot.service'; 3 | import { QueueProvidersHelper } from './helpers'; 4 | import { ProcessorExplorer } from './processor.explorer'; 5 | import { CarrotModuleOptions } from './types'; 6 | import { CARROT_MODULE_OPTIONS } from './constants'; 7 | import { DiscoveryModule } from '@nestjs/core'; 8 | 9 | @Module({}) 10 | export class CarrotModule { 11 | static forRoot(options: CarrotModuleOptions): DynamicModule { 12 | const optionsProvider = { 13 | provide: CARROT_MODULE_OPTIONS, 14 | useValue: options, 15 | }; 16 | return { 17 | global: options.global, 18 | module: CarrotModule, 19 | imports: [DiscoveryModule], 20 | providers: [CarrotService, ProcessorExplorer, optionsProvider], 21 | exports: [optionsProvider], 22 | }; 23 | } 24 | 25 | /** 26 | * Inject the channel provider and register a set 27 | * of queue names to be initialized upon module import. 28 | */ 29 | static forFeature(...queueNames: string[]): DynamicModule { 30 | const provider = QueueProvidersHelper.createProvider(queueNames); 31 | return { 32 | module: CarrotModule, 33 | providers: [CarrotService, provider], 34 | exports: [provider], 35 | }; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/carrot.service.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable } from '@nestjs/common'; 2 | import { 3 | AmqpConnectionManager, 4 | ChannelWrapper, 5 | connect, 6 | } from 'amqp-connection-manager'; 7 | import { ConfirmChannel } from 'amqplib'; 8 | import { CarrotModuleOptions } from './types'; 9 | import { CARROT_MODULE_OPTIONS } from './constants'; 10 | 11 | @Injectable() 12 | export class CarrotService { 13 | private readonly connection: AmqpConnectionManager; 14 | 15 | constructor( 16 | @Inject(CARROT_MODULE_OPTIONS) 17 | private readonly options: CarrotModuleOptions, 18 | ) { 19 | this.connection = connect(this.options.url, this.options.connectionOptions); 20 | } 21 | 22 | getPublisherChannel(queueNames: string[]): ChannelWrapper { 23 | return this.getChannel('PUBLISHER_CHANNEL', queueNames); 24 | } 25 | 26 | getConsumerChannel(queueNames: string[]): ChannelWrapper { 27 | return this.getChannel('CONSUMER_CHANNEL', queueNames); 28 | } 29 | 30 | private getChannel(name: string, queues: string[]): ChannelWrapper { 31 | return this.connection.createChannel({ 32 | name: name, 33 | json: true, 34 | setup: async (channel: ConfirmChannel) => { 35 | return Promise.all( 36 | queues.map((queue) => 37 | channel.assertQueue(queue, { 38 | durable: true, 39 | }), 40 | ), 41 | ); 42 | }, 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nestjs-carrot", 3 | "version": "0.1.1", 4 | "description": "A RabbitMQ module for Nest.js", 5 | "author": "shayan-shojaei", 6 | "license": "MIT", 7 | "scripts": { 8 | "build": "rimraf -rf dist && tsc -p tsconfig.json", 9 | "format": "prettier --write \"lib/**/*.ts\"", 10 | "lint": "eslint \"lib/**/*.ts\" --fix", 11 | "test": "jest", 12 | "test:watch": "jest --watch", 13 | "test:cov": "jest --coverage", 14 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 15 | "test:e2e": "jest --config ./test/jest-e2e.json" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/shayan-shojaei/nestjs-carrot" 20 | }, 21 | "peerDependencies": { 22 | "@nestjs/common": "^9.0.0 || ^10.0.0 || ^11.0.0", 23 | "@nestjs/core": "^9.0.0 || ^10.0.0 || ^11.0.0", 24 | "reflect-metadata": "^0.2.2", 25 | "rxjs": "^7.8.1", 26 | "amqp-connection-manager": "^4.1.14", 27 | "amqplib": "^0.10.5" 28 | }, 29 | "devDependencies": { 30 | "@nestjs/common": "^11.0.1", 31 | "@nestjs/core": "^11.0.1", 32 | "amqp-connection-manager": "^4.1.14", 33 | "amqplib": "^0.10.5", 34 | "@types/supertest": "^6.0.2", 35 | "@eslint/eslintrc": "^3.2.0", 36 | "@eslint/js": "^9.18.0", 37 | "@nestjs/cli": "^11.0.0", 38 | "@nestjs/schematics": "^11.0.0", 39 | "@nestjs/testing": "^11.0.1", 40 | "@swc/cli": "^0.6.0", 41 | "@swc/core": "^1.10.7", 42 | "@types/amqplib": "^0.10.6", 43 | "@types/express": "^5.0.0", 44 | "@types/jest": "^29.5.14", 45 | "@types/node": "^22.10.7", 46 | "eslint": "^9.18.0", 47 | "eslint-config-prettier": "^10.0.1", 48 | "eslint-plugin-prettier": "^5.2.2", 49 | "globals": "^15.14.0", 50 | "jest": "^29.7.0", 51 | "prettier": "^3.4.2", 52 | "reflect-metadata": "^0.2.2", 53 | "rimraf": "^6.0.1", 54 | "rxjs": "^7.8.1", 55 | "source-map-support": "^0.5.21", 56 | "supertest": "^7.0.0", 57 | "ts-jest": "^29.2.6", 58 | "ts-loader": "^9.5.2", 59 | "ts-node": "^10.9.2", 60 | "tsconfig-paths": "^4.2.0", 61 | "typescript": "^5.7.3", 62 | "typescript-eslint": "^8.20.0" 63 | }, 64 | "files": [ 65 | "dist", 66 | "index.*" 67 | ], 68 | "keywords": [ 69 | "nestjs", 70 | "rabbitmq", 71 | "amqp", 72 | "carrot", 73 | "nestjs-carrot" 74 | ] 75 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Carrot 🥕 2 | 3 | ![nestjs-carrot banner](https://github.com/shayan-shojaei/nestjs-carrot/blob/main/static/banner.png?raw=true) 4 | 5 | A NestJS package that provides a RabbitMQ-based queue system, inspired by [@nestjs/bullmq](https://github.com/nestjs/bull). This package allows you to easily integrate RabbitMQ into your NestJS application for background job processing, task scheduling, and message queuing. 6 | 7 | ## Installation 8 | 9 | ```bash 10 | npm install nestjs-carrot 11 | ``` 12 | 13 | ## Usage 14 | 15 | ### Importing the Module 16 | 17 | ```typescript 18 | import { Module } from '@nestjs/common'; 19 | import { RabbitMQModule } from 'nestjs-carrot'; 20 | 21 | @Module({ 22 | imports: [ 23 | RabbitMQModule.forRoot({ 24 | url: ['amqp://127.0.0.1:5672'], // RabbitMQ connection URL(s) 25 | global: true, // Whether to create a global connection 26 | }), 27 | ], 28 | }) 29 | export class AppModule {} 30 | ``` 31 | 32 | ### Queue Processors 33 | 34 | Create a queue processor by decorating a class with the `QueueProcessor` decorator. The decorator takes a required argument, the name of the queue to process jobs from; and an optional argument to specify whether the payload 35 | should be parsed as JSON, string or buffer. 36 | 37 | ```typescript 38 | import { QueueProcessor, IQueueProcessor } from 'nestjs-carrot'; 39 | 40 | @QueueProcessor('my-queue', { type: 'json' }) // or 'string' or 'buffer' 41 | export class MyQueueProcessor implements IQueueProcessor { 42 | async process(job: Job) { 43 | console.log(job.data); 44 | } 45 | } 46 | ``` 47 | 48 | You also need to register the queue processor in your module. 49 | 50 | ```typescript 51 | import { Module } from '@nestjs/common'; 52 | import { MyQueueProcessor } from './my-queue.processor'; 53 | 54 | @Module({ 55 | providers: [MyQueueProcessor], 56 | }) 57 | export class SomeModule {} 58 | ``` 59 | 60 | ### Queue Producers 61 | 62 | Import the channel provider and use it to send messages to a queue, specifying the queue names, which would be used to assert the queues 63 | and creating the channel. 64 | 65 | ```typescript 66 | import { Module } from '@nestjs/common'; 67 | import { CarrotModule } from 'nestjs-carrot'; 68 | 69 | @Module({ 70 | imports: [ 71 | CarrotModule.forFeature('my-queue', 'another-queue'), 72 | ], 73 | }) 74 | export class SomeModule {} 75 | ``` 76 | 77 | Then inject the provider and use it to send messages to the queue. 78 | 79 | ```typescript 80 | import { Injectable } from '@nestjs/common'; 81 | import { InjectChannel } from 'nestjs-carrot'; 82 | import { ChannelWrapper } from 'amqp-connection-manager'; 83 | 84 | @Injectable() 85 | export class SomeService { 86 | constructor(@InjectChannel() private readonly channel: ChannelWrapper) {} 87 | 88 | async sendMessage() { 89 | await this.channel.sendToQueue('my-queue', { message: 'Hello, World!' }); 90 | } 91 | } 92 | ``` 93 | 94 | ## Contributing 95 | 96 | Contributions are welcome! Please open an issue or submit a pull request if you have any improvements or bug fixes. 97 | 98 | ## License 99 | 100 | This project is licensed under the MIT License. See the [LICENSE](LICENSE.md) file for details. 101 | -------------------------------------------------------------------------------- /lib/processor.explorer.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, OnModuleInit } from '@nestjs/common'; 2 | import { DiscoveryService, Reflector } from '@nestjs/core'; 3 | import { IQueueProcessor } from './interfaces'; 4 | import { 5 | QUEUE_PROCESSOR_METADATA, 6 | QUEUE_PROCESSOR_PAYLOAD_TYPE_METADATA, 7 | } from './decorators'; 8 | import { CarrotService } from './carrot.service'; 9 | import { QueueProcessorPayloadType } from './types'; 10 | import { ChannelWrapper } from 'amqp-connection-manager'; 11 | 12 | @Injectable() 13 | export class ProcessorExplorer implements OnModuleInit { 14 | /** 15 | * A map of queue name to processor instances. 16 | */ 17 | private readonly processors = new Map(); 18 | 19 | constructor( 20 | private readonly discoveryService: DiscoveryService, 21 | private readonly reflector: Reflector, 22 | private readonly carrotService: CarrotService, 23 | ) {} 24 | 25 | async onModuleInit(): Promise { 26 | this.explore(); 27 | await this.setupConsumers(); 28 | } 29 | 30 | private explore(): void { 31 | const discoveredProcessors = this.discoveryService 32 | .getProviders() 33 | .filter((provider) => { 34 | return ( 35 | provider.metatype && 36 | this.reflector.get( 37 | QUEUE_PROCESSOR_METADATA, 38 | provider.metatype, 39 | ) 40 | ); 41 | }); 42 | 43 | for (const instanceWrapper of discoveredProcessors) { 44 | const queue = this.reflector.get( 45 | QUEUE_PROCESSOR_METADATA, 46 | instanceWrapper.metatype!, // metatype is never null here 47 | ); 48 | if (!queue) { 49 | continue; 50 | } 51 | if (!this.processors.has(queue)) { 52 | this.processors.set(queue, []); 53 | } 54 | this.processors.get(queue)!.push(instanceWrapper.instance); 55 | } 56 | } 57 | 58 | private async setupConsumers(): Promise { 59 | const channel = this.carrotService.getConsumerChannel( 60 | Array.from(this.processors.keys()), 61 | ); 62 | 63 | await Promise.all( 64 | Array.from(this.processors.entries()).map(async ([queue, processors]) => 65 | this.consume(channel, queue, processors), 66 | ), 67 | ); 68 | } 69 | 70 | private async consume( 71 | channel: ChannelWrapper, 72 | queue: string, 73 | processors: IQueueProcessor[], 74 | ): Promise { 75 | // TODO: handle ack/nack/requeue 76 | await channel.consume( 77 | queue, 78 | async (message) => { 79 | try { 80 | await Promise.all( 81 | processors.map(async (processor) => { 82 | const payloadType = this.reflector.get( 83 | QUEUE_PROCESSOR_PAYLOAD_TYPE_METADATA, 84 | processor.constructor, 85 | ); 86 | 87 | // Content is a buffer by default but could be converted 88 | // to a string or JSON object based (any) on the payload 89 | // type. 90 | let payload: Buffer | string | any = message.content; 91 | 92 | switch (payloadType) { 93 | case 'string': 94 | payload = message.content.toString(); 95 | break; 96 | case 'json': 97 | payload = JSON.parse(message.content.toString()); 98 | break; 99 | default: 100 | break; 101 | } 102 | 103 | return processor.process(payload); 104 | }), 105 | ); 106 | } catch (error) { 107 | console.error(error); 108 | } 109 | }, 110 | { 111 | noAck: true, 112 | }, 113 | ); 114 | } 115 | } 116 | --------------------------------------------------------------------------------