├── img └── logo.png ├── .vscode └── settings.json ├── .npmignore ├── .husky └── pre-commit ├── .lintstagedrc ├── lib ├── interfaces │ ├── rmqService.ts │ ├── serdes.interface.ts │ ├── error.headers.interface.ts │ ├── middleware.interface.ts │ ├── index.ts │ ├── interceptor.interface.ts │ ├── metatags.ts │ └── rmq-options.interface.ts ├── common │ ├── get-uniqId.ts │ ├── index.ts │ ├── serdes.ts │ ├── toRegex.ts │ ├── logger.ts │ ├── extended.providers.ts │ ├── error.class.ts │ └── events.discovery.ts ├── decorators │ ├── validate.decorator.ts │ ├── index.ts │ ├── interceptor.decorator.ts │ ├── serdes.decorator.ts │ ├── middleware.decorator.ts │ └── rmq-message.decorator.ts ├── index.ts ├── rmq-core.module.ts ├── rmq.module.ts ├── constants.ts ├── rmq.global.service.ts ├── rmq-connect.service.ts └── rmq.service.ts ├── tsconfig.build.json ├── .auto-changelog ├── .prettierrc ├── test ├── mocks │ ├── dto │ │ └── myClass.dto.ts │ ├── error.handlers.ts │ ├── event.middleware.ts │ ├── rmq-nestjs.module.ts │ ├── rmq.controller.ts │ ├── event.interceptor.ts │ └── rmq.event.ts └── rmq-nestjs.spec.ts ├── jest.config.js ├── commitlint.config.js ├── .github ├── dependabot.yml └── workflows │ ├── main.yml │ └── release.yml ├── CONTRIBUTING.md ├── .release-it.json ├── tsconfig.json ├── .gitignore ├── .eslintrc.js ├── LICENSE ├── package.json ├── README.md └── CHANGELOG.md /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DIY0R/nestjs-rabbitmq/HEAD/img/logo.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.useFlatConfig": true, 3 | "prettier.enable": true 4 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /tsconfig.json 2 | /tsconfig.build.json 3 | /node_modules 4 | /lib 5 | /test-file 6 | /img -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | npx --no-install commitlint --edit "$1" 4 | npm run lint 5 | npm run test -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "**/*.{js,ts}": [ 3 | "eslint --max-warnings=0", 4 | "prettier --write" 5 | ] 6 | } -------------------------------------------------------------------------------- /lib/interfaces/rmqService.ts: -------------------------------------------------------------------------------- 1 | export interface IResult { 2 | content: any; 3 | headers: Record; 4 | } 5 | -------------------------------------------------------------------------------- /lib/common/get-uniqId.ts: -------------------------------------------------------------------------------- 1 | import { randomUUID } from 'node:crypto'; 2 | export const getUniqId = (): string => randomUUID(); 3 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /lib/interfaces/serdes.interface.ts: -------------------------------------------------------------------------------- 1 | export interface ISerDes { 2 | deserialize: (message: Buffer) => any; 3 | serialize: (message: any) => Buffer; 4 | } 5 | -------------------------------------------------------------------------------- /.auto-changelog: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreCommitPattern": "^chore", 3 | "output": "CHANGELOG.md", 4 | "template": "compact", 5 | "commitLimit": false, 6 | "hideCredit": true 7 | } -------------------------------------------------------------------------------- /lib/decorators/validate.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | import { RMQ_VALIDATE } from '../constants'; 3 | 4 | export const RMQValidate = () => SetMetadata(RMQ_VALIDATE, true); 5 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './rmq.module'; 2 | export * from './rmq.service'; 3 | export * from './rmq.global.service'; 4 | export * from './decorators'; 5 | export * from './interfaces'; 6 | export * from './common'; 7 | -------------------------------------------------------------------------------- /lib/interfaces/error.headers.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IRmqErrorHeaders { 2 | '-x-error': string; 3 | '-x-date': string; 4 | '-x-service': string; 5 | '-x-host': string; 6 | '-x-status-code': number; 7 | } 8 | -------------------------------------------------------------------------------- /lib/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export * from './rmq-message.decorator'; 2 | export * from './serdes.decorator'; 3 | export * from './interceptor.decorator'; 4 | export * from './middleware.decorator'; 5 | export * from './validate.decorator'; 6 | -------------------------------------------------------------------------------- /lib/decorators/interceptor.decorator.ts: -------------------------------------------------------------------------------- 1 | import { UseInterceptors } from '@nestjs/common'; 2 | import { TypeRmqInterceptor } from 'lib/interfaces'; 3 | 4 | export const RmqInterceptor = (interceptors: TypeRmqInterceptor) => UseInterceptors(interceptors); 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "tabWidth": 2, 5 | "printWidth": 100, 6 | "trailingComma": "all", 7 | "bracketSpacing": true, 8 | "arrowParens": "avoid", 9 | "endOfLine": "auto" 10 | } 11 | 12 | -------------------------------------------------------------------------------- /lib/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from './events.discovery'; 2 | export * from './error.class'; 3 | export * from './extended.providers'; 4 | export * from './get-uniqId'; 5 | export * from './logger'; 6 | export * from './serdes'; 7 | export * from './toRegex'; 8 | -------------------------------------------------------------------------------- /lib/decorators/serdes.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | import { SER_DAS_KEY } from '../constants'; 3 | import { ISerDes } from '../interfaces'; 4 | 5 | export const SerDes = (options: ISerDes) => SetMetadata(SER_DAS_KEY, options); 6 | -------------------------------------------------------------------------------- /lib/common/serdes.ts: -------------------------------------------------------------------------------- 1 | import { ISerDes } from 'lib/interfaces'; 2 | 3 | export const defaultSerDes: ISerDes = { 4 | deserialize: (message: Buffer): any => JSON.parse(message.toString()), 5 | serialize: (message: any): Buffer => Buffer.from(JSON.stringify(message)), 6 | }; 7 | -------------------------------------------------------------------------------- /test/mocks/dto/myClass.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsInt, IsString, MaxLength } from 'class-validator'; 2 | 3 | export class MyClass { 4 | @IsString() 5 | @MaxLength(5, { message: 'The name must be less than 5' }) 6 | name: string; 7 | 8 | @IsInt() 9 | age: number; 10 | } 11 | -------------------------------------------------------------------------------- /lib/interfaces/middleware.interface.ts: -------------------------------------------------------------------------------- 1 | import { ConsumeMessage } from 'amqplib'; 2 | export type NextFunction = () => void; 3 | export abstract class IRmqMiddleware { 4 | abstract use(message: ConsumeMessage, content: any): any; 5 | } 6 | export type TypeRmqMiddleware = typeof IRmqMiddleware; 7 | -------------------------------------------------------------------------------- /lib/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from './rmq-options.interface'; 2 | export * from './interceptor.interface'; 3 | export * from './middleware.interface'; 4 | export * from './error.headers.interface'; 5 | export * from './serdes.interface'; 6 | export * from './metatags'; 7 | export * from './rmqService'; 8 | -------------------------------------------------------------------------------- /lib/decorators/middleware.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | import { MIDDLEWARES_METADATA } from '../constants'; 3 | import { TypeRmqMiddleware } from 'lib/interfaces'; 4 | 5 | export const RmqMiddleware = (options: TypeRmqMiddleware) => 6 | SetMetadata(MIDDLEWARES_METADATA, options); 7 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.ts$', 6 | verbose: true, 7 | silent: false, 8 | moduleFileExtensions: ['ts', 'js', 'json', 'node'], 9 | }; 10 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'type-enum': [ 5 | 2, 6 | 'always', 7 | ['feat', 'fix', 'refact', 'chore', 'doc', 'test'], 8 | ], 9 | 'subject-case': [2, 'never', ['start-case', 'pascal-case', 'upper-case']], 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | allow: 8 | - dependency-type: "all" 9 | commit-message: 10 | prefix: "chore" 11 | ignore: 12 | - dependency-name: "*" 13 | update-types: ["version-update:semver-patch"] 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hi! I would be very happy if you could help develop this project. It is very important to me. 4 | 5 | Here are a few rules to follow: 6 | 7 | 1. Before submitting a pull request, check the correctness of the commit prefixes. Choose the most appropriate prefix for your changes by referring to the `commitlint.config.js` file. 8 | 2. Make sure all tests pass successfully! 9 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/release-it/schema/release-it.json", 3 | "git": { 4 | "commitMessage": "chore: version v${version}" 5 | }, 6 | "npm":{ 7 | "release": true 8 | }, 9 | "github": { 10 | "release": true 11 | }, 12 | "hooks": { 13 | "before:init":["git pull","npm run test"], 14 | "after:bump":"npx auto-changelog -p" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/interfaces/interceptor.interface.ts: -------------------------------------------------------------------------------- 1 | import { ConsumeMessage } from 'amqplib'; 2 | 3 | export type ReverseFunction = (content: any, message: ConsumeMessage) => Promise; 4 | export abstract class IRmqInterceptor { 5 | constructor(...injects: any[]) {} 6 | abstract intercept(content: any, message: ConsumeMessage): Promise; 7 | } 8 | export type TypeRmqInterceptor = typeof IRmqInterceptor; 9 | -------------------------------------------------------------------------------- /lib/decorators/rmq-message.decorator.ts: -------------------------------------------------------------------------------- 1 | import { IDescriptorRoute } from 'lib/interfaces'; 2 | import { NON_ROUTE, RMQ_MESSAGE_META_TAG } from '../constants'; 3 | 4 | export const reflectFunction = (event: string) => 5 | function (target: any, propertyKey: string | symbol, descriptor: IDescriptorRoute) { 6 | Reflect.defineMetadata(RMQ_MESSAGE_META_TAG, event, descriptor.value); 7 | }; 8 | 9 | export function MessageRoute(event: string) { 10 | return reflectFunction(event); 11 | } 12 | export function MessageNonRoute() { 13 | return reflectFunction(NON_ROUTE); 14 | } 15 | -------------------------------------------------------------------------------- /lib/common/toRegex.ts: -------------------------------------------------------------------------------- 1 | export const toRegex = (pattern: string): RegExp => { 2 | const word = '[a-z]+'; 3 | const normalizedPattern = pattern.replace(/#(?:\.#)+/g, '#'); 4 | const withWildcardReplaced = normalizedPattern.replace(/\*/g, word); 5 | if (withWildcardReplaced === '#') { 6 | return new RegExp(`(?:${word}(?:\\.${word})*)?`); 7 | } 8 | const withHashReplaced = withWildcardReplaced 9 | .replace(/^#\./, `(?:${word}\\.)*`) 10 | .replace(/\.#/g, `(?:\\.${word})*`); 11 | const escapedDots = withHashReplaced.replace(/(? any | void; 6 | export interface MetaTagEndpoint { 7 | handler: IConsumeFunction; 8 | serdes?: ISerDes | undefined; 9 | interceptors: CallbackFunctionVariadic[]; 10 | middlewares: TypeRmqMiddleware[]; 11 | validate: null; 12 | } 13 | export type IMetaTagsMap = Map; 14 | export interface IDescriptorRoute { 15 | value?: IConsumeFunction; 16 | } 17 | 18 | export type CallbackFunctionVariadic = (...args: any[]) => any; 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2021", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "skipLibCheck": true, 14 | "strictNullChecks": false, 15 | "noImplicitAny": false, 16 | "strictBindCallApply": false, 17 | "forceConsistentCasingInFileNames": false, 18 | "noFallthroughCasesInSwitch": false, 19 | }, 20 | "include": [ 21 | "lib/**/*", 22 | "test/**/*", 23 | "./" 24 | ], 25 | "ignorePatterns": [ 26 | "commitlint.config.js" 27 | ], 28 | "exclude": [ 29 | "node_modules" 30 | ] 31 | } -------------------------------------------------------------------------------- /test/mocks/error.handlers.ts: -------------------------------------------------------------------------------- 1 | import { MessagePropertyHeaders } from 'amqplib'; 2 | import { IRmqErrorHeaders, RMQError } from '../../lib'; 3 | 4 | export class MyRMQErrorHandler { 5 | public static handle(headers: IRmqErrorHeaders | MessagePropertyHeaders): Error | RMQError { 6 | return new RMQError( 7 | headers['-x-error'], 8 | headers['-x-service'], 9 | headers['-x-status-code'], 10 | headers['-x-host'], 11 | new Date().getMonth().toString(), 12 | ); 13 | } 14 | } 15 | export class MyGlobalRMQErrorHandler { 16 | public static handle(headers: IRmqErrorHeaders | MessagePropertyHeaders): Error | RMQError { 17 | return new RMQError( 18 | headers['-x-error'], 19 | headers['-x-service'], 20 | headers['-x-status-code'], 21 | headers['-x-host'], 22 | new Date().getDate().toString(), 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.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 | 19 | /tests-app 20 | # Tests 21 | /coverage 22 | /.nyc_output 23 | 24 | # IDEs and editors 25 | /.idea 26 | .project 27 | .classpath 28 | .c9/ 29 | *.launch 30 | .settings/ 31 | *.sublime-workspace 32 | 33 | # IDE - VSCode 34 | .vscode/* 35 | !.vscode/settings.json 36 | !.vscode/tasks.json 37 | !.vscode/launch.json 38 | !.vscode/extensions.json 39 | 40 | # dotenv environment variable files 41 | .env 42 | .env.development.local 43 | .env.test.local 44 | .env.production.local 45 | .env.local 46 | 47 | # temp directory 48 | .temp 49 | .tmp 50 | 51 | # Runtime data 52 | pids 53 | *.pid 54 | *.seed 55 | *.pid.lock 56 | 57 | # Diagnostic reports (https://nodejs.org/api/report.html) 58 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 59 | test-file 60 | -------------------------------------------------------------------------------- /test/mocks/event.middleware.ts: -------------------------------------------------------------------------------- 1 | import { ConsumeMessage } from 'amqplib'; 2 | import { IRmqMiddleware } from '../../lib'; 3 | 4 | export class EventMiddlewareModule implements IRmqMiddleware { 5 | async use(message: ConsumeMessage, content: any): Promise { 6 | if (content?.arrayMiddleware) content.arrayMiddleware.push(1); 7 | } 8 | } 9 | 10 | export class EventMiddlewareClass implements IRmqMiddleware { 11 | async use(message: ConsumeMessage, content: any): Promise { 12 | if (content?.arrayMiddleware) content.arrayMiddleware.push(2); 13 | } 14 | } 15 | 16 | export class EventMiddlewareEndpoint implements IRmqMiddleware { 17 | async use(message: ConsumeMessage, content: any): Promise { 18 | if (content?.arrayMiddleware) content.arrayMiddleware.push(3); 19 | } 20 | } 21 | export class EventMiddlewareEndpointReturn implements IRmqMiddleware { 22 | async use(message: ConsumeMessage, content: any): Promise { 23 | return { return: true }; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/common/logger.ts: -------------------------------------------------------------------------------- 1 | import { Logger, LoggerService } from '@nestjs/common'; 2 | import { blueBright, white, yellow } from 'chalk'; 3 | 4 | export class RQMColorLogger implements LoggerService { 5 | logMessages: boolean; 6 | 7 | constructor(logMessages: boolean) { 8 | this.logMessages = logMessages ?? false; 9 | } 10 | 11 | log(message: any, context?: string): any { 12 | Logger.log(message, context); 13 | } 14 | 15 | error(message: any, context?: string): any { 16 | Logger.error(message, context); 17 | } 18 | 19 | debug(message: any, context?: string): any { 20 | if (!this.logMessages) return; 21 | const msg = JSON.stringify(message); 22 | const action = context.split(',')[0]; 23 | const topic = context.split(',')[1]; 24 | Logger.log(`${blueBright(action)} [${yellow(topic)}] ${white(msg)}`); 25 | console.warn(`${blueBright(action)} [${yellow(topic)}] ${white(msg)}`); 26 | } 27 | 28 | warn(message: any, context?: string): any { 29 | Logger.warn(message, context); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir: __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin', 'prettier'], 9 | extends: ['plugin:@typescript-eslint/recommended', 'prettier'], 10 | root: true, 11 | env: { 12 | node: true, 13 | jest: true, 14 | }, 15 | ignorePatterns: ['.eslintrc.js'], 16 | rules: { 17 | 'prettier/prettier': [ 18 | 'error', 19 | { 20 | endOfLine: 'auto', 21 | }, 22 | ], 23 | curly: ['error', 'multi-line'], 24 | 'lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }], 25 | '@typescript-eslint/interface-name-prefix': 'off', 26 | '@typescript-eslint/explicit-function-return-type': 'off', 27 | '@typescript-eslint/explicit-module-boundary-types': 'off', 28 | '@typescript-eslint/no-unused-vars': 'off', 29 | '@typescript-eslint/no-explicit-any': 'off', 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /lib/common/extended.providers.ts: -------------------------------------------------------------------------------- 1 | import { ClassProvider, Provider, Type, ValueProvider } from '@nestjs/common'; 2 | import { getUniqId } from './get-uniqId'; 3 | import { INTERCEPTORS, MIDDLEWARES, SERDES } from '../constants'; 4 | import { IExtendedBroker } from '..//interfaces'; 5 | import { defaultSerDes } from './serdes'; 6 | 7 | export const extendedProvidersArr = (extendedOptions: IExtendedBroker): Provider[] => { 8 | const { interceptors = [], middlewares = [], serDes } = extendedOptions; 9 | const interceptorProviders: ClassProvider[] = interceptors.map(useClass => ({ 10 | provide: getUniqId(), 11 | useClass: useClass as Type, 12 | })); 13 | const interceptorsProvider: ValueProvider = { 14 | provide: INTERCEPTORS, 15 | useValue: interceptorProviders.map(provider => provider.provide), 16 | }; 17 | return [ 18 | { provide: SERDES, useValue: serDes ?? defaultSerDes }, 19 | { provide: MIDDLEWARES, useValue: middlewares ?? [] }, 20 | interceptorsProvider, 21 | ...interceptorProviders, 22 | ]; 23 | }; 24 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: npm publish 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - '*' 8 | jobs: 9 | publish-npm: 10 | environment: publish-npm 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-node@v3 15 | with: 16 | node-version: 20 17 | registry-url: https://registry.npmjs.org/ 18 | - run: npm install 19 | - run: npm run build 20 | - run: npm publish 21 | env: 22 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 23 | publish-gpr: 24 | environment: publish-gpr 25 | runs-on: ubuntu-latest 26 | permissions: 27 | contents: read 28 | packages: write 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: actions/setup-node@v4 32 | with: 33 | node-version: '20.x' 34 | registry-url: 'https://npm.pkg.github.com' 35 | - run: npm install 36 | - run: npm run build 37 | - run: npm publish 38 | env: 39 | NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Sobirov Diyorbek 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 | -------------------------------------------------------------------------------- /test/mocks/rmq-nestjs.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { RmqModule } from '../../lib'; 3 | import { RmqEvents } from './rmq.event'; 4 | import { RmqServiceController } from './rmq.controller'; 5 | import { EventInterceptorModule } from './event.interceptor'; 6 | import { EventMiddlewareModule } from './event.middleware'; 7 | import { MyRMQErrorHandler } from './error.handlers'; 8 | 9 | @Module({ 10 | imports: [ 11 | RmqModule.forFeatureAsync({ 12 | useFactory: async () => ({ 13 | exchange: { 14 | exchange: 'for-test', 15 | type: 'topic', 16 | 17 | options: { 18 | durable: true, 19 | autoDelete: true, 20 | }, 21 | }, 22 | queue: { 23 | queue: 'test-for', 24 | options: { durable: true }, 25 | consumeOptions: { noAck: false }, 26 | }, 27 | 28 | replyTo: { 29 | queue: '', 30 | options: { exclusive: true }, 31 | consumeOptions: { noAck: true }, 32 | errorHandler: MyRMQErrorHandler, 33 | }, 34 | serviceName: 'Connection-Service-Spec', 35 | }), 36 | interceptors: [EventInterceptorModule], 37 | middlewares: [EventMiddlewareModule], 38 | }), 39 | ], 40 | providers: [RmqEvents, RmqServiceController], 41 | exports: [RmqServiceController, RmqModule], 42 | }) 43 | export class ConnectionMockModule {} 44 | -------------------------------------------------------------------------------- /lib/rmq-core.module.ts: -------------------------------------------------------------------------------- 1 | import { DynamicModule, Module, Global } from '@nestjs/common'; 2 | import { RMQ_OPTIONS } from './constants'; 3 | import { IRMQOptions, IRMQOptionsAsync } from './interfaces'; 4 | import { RmqNestjsConnectService } from './rmq-connect.service'; 5 | import { RmqGlobalService } from './rmq.global.service'; 6 | import { RmqErrorGlobalService } from './common'; 7 | 8 | @Global() 9 | @Module({ 10 | providers: [RmqErrorGlobalService], 11 | }) 12 | export class RmqNestjsCoreModule { 13 | static forRoot(RMQOptions: IRMQOptions): DynamicModule { 14 | return { 15 | module: RmqNestjsCoreModule, 16 | providers: [ 17 | { provide: RMQ_OPTIONS, useValue: RMQOptions }, 18 | RmqNestjsConnectService, 19 | RmqGlobalService, 20 | ], 21 | exports: [RmqNestjsConnectService, RmqGlobalService, RMQ_OPTIONS], 22 | }; 23 | } 24 | 25 | static forRootAsync(RMQOptionsAsync: IRMQOptionsAsync): DynamicModule { 26 | return { 27 | module: RmqNestjsCoreModule, 28 | imports: RMQOptionsAsync.imports, 29 | providers: [ 30 | { 31 | provide: RMQ_OPTIONS, 32 | useFactory: async (...args: any[]) => await RMQOptionsAsync.useFactory(...args), 33 | inject: RMQOptionsAsync.inject || [], 34 | }, 35 | RmqNestjsConnectService, 36 | RmqGlobalService, 37 | ], 38 | exports: [RmqNestjsConnectService, RmqGlobalService, RMQ_OPTIONS], 39 | }; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/mocks/rmq.controller.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { RmqService, RmqGlobalService } from '../../lib'; 3 | 4 | @Injectable() 5 | export class RmqServiceController { 6 | constructor( 7 | private readonly rmqService: RmqService, 8 | private readonly rmqGlobalService: RmqGlobalService, 9 | ) {} 10 | 11 | async sendMessage(obj: Record, topic: string) { 12 | const sendhi = await this.rmqService.send(topic, obj); 13 | return sendhi; 14 | } 15 | 16 | async sendMessageWithProvider(obj: Record, topic: string = 'text.interceptor') { 17 | const sendhi = await this.rmqService.send(topic, obj); 18 | return sendhi; 19 | } 20 | 21 | async sendGlobal(obj: Record, topic: string) { 22 | const message = await this.rmqGlobalService.send( 23 | 'for-test', 24 | topic, 25 | obj, 26 | ); 27 | return message; 28 | } 29 | 30 | sendNotify(obj: Record) { 31 | const message = this.rmqGlobalService.notify('for-test', 'notify.global', obj); 32 | return message; 33 | } 34 | 35 | sendNotifyService(obj: Record) { 36 | const message = this.rmqService.notify('notify.global', obj); 37 | return message; 38 | } 39 | 40 | async sendToQueue(queue: string, obj: Record) { 41 | const status = await this.rmqGlobalService.sendToQueue(queue, obj); 42 | return status; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/mocks/event.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { ConsumeMessage } from 'amqplib'; 2 | import { Injectable } from '@nestjs/common'; 3 | import { RmqService, ReverseFunction, IRmqInterceptor } from '../../lib'; 4 | 5 | @Injectable() 6 | export class EventInterceptorModule implements IRmqInterceptor { 7 | async intercept(message: ConsumeMessage, content: any): Promise { 8 | if (content?.arrayInterceptor) content.arrayInterceptor.push(1); 9 | return async (content: any, message: ConsumeMessage) => { 10 | if (content?.arrayInterceptor) content.arrayInterceptor.push(6); 11 | }; 12 | } 13 | } 14 | @Injectable() 15 | export class EventInterceptorClass implements IRmqInterceptor { 16 | async intercept(message: ConsumeMessage, content: any): Promise { 17 | if (content?.arrayInterceptor) content.arrayInterceptor.push(2); 18 | return async (content: any, message: ConsumeMessage) => { 19 | if (content?.arrayInterceptor) content.arrayInterceptor.push(5); 20 | }; 21 | } 22 | } 23 | @Injectable() 24 | export class EventInterceptorEndpoint implements IRmqInterceptor { 25 | constructor(private readonly rmqSerivce: RmqService) {} 26 | async intercept(message: ConsumeMessage, content: any): Promise { 27 | content.arrayInterceptor.push(3); 28 | return async (content: any, message: ConsumeMessage) => { 29 | const { number } = await this.rmqSerivce.send('text.number', { 30 | number: 4, 31 | }); 32 | content.arrayInterceptor.push(number); 33 | }; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/rmq.module.ts: -------------------------------------------------------------------------------- 1 | import { DynamicModule, Module, ModuleMetadata, Provider } from '@nestjs/common'; 2 | import { RmqService } from './rmq.service'; 3 | import { DiscoveryModule } from '@nestjs/core'; 4 | import { MetaTagsScannerService, RmqErrorService, extendedProvidersArr, getUniqId } from './common'; 5 | import { RmqNestjsCoreModule } from './rmq-core.module'; 6 | import { 7 | IModuleBroker, 8 | IModuleBrokerAsync, 9 | ImportsType, 10 | IRMQOptions, 11 | IRMQOptionsAsync, 12 | } from './interfaces'; 13 | import { MODULE_TOKEN, RMQ_BROKER_OPTIONS } from './constants'; 14 | 15 | @Module({ 16 | providers: [{ provide: MODULE_TOKEN, useFactory: getUniqId }], 17 | }) 18 | export class RmqModule { 19 | static forRoot(RMQOptions: IRMQOptions): DynamicModule { 20 | return { 21 | module: RmqModule, 22 | imports: [RmqNestjsCoreModule.forRoot(RMQOptions)], 23 | }; 24 | } 25 | 26 | static forRootAsync(RMQOptionsAsync: IRMQOptionsAsync): DynamicModule { 27 | return { 28 | module: RmqModule, 29 | imports: [RmqNestjsCoreModule.forRootAsync(RMQOptionsAsync)], 30 | }; 31 | } 32 | 33 | static forFeature(options: IModuleBroker): DynamicModule { 34 | const providerOptions = { provide: RMQ_BROKER_OPTIONS, useValue: options }; 35 | const providersExtended = extendedProvidersArr(options); 36 | return this.generateForFeature(providerOptions, providersExtended); 37 | } 38 | 39 | static forFeatureAsync(options: IModuleBrokerAsync): DynamicModule { 40 | const providerOptions = { 41 | provide: RMQ_BROKER_OPTIONS, 42 | useFactory: async (...args: any[]) => await options.useFactory(...args), 43 | inject: options.inject || [], 44 | }; 45 | const providersExtended = extendedProvidersArr(options); 46 | return this.generateForFeature(providerOptions, providersExtended, options.imports); 47 | } 48 | 49 | private static generateForFeature( 50 | providerOptions: Provider, 51 | providersExtended: Provider[], 52 | imports: ImportsType = [], 53 | ): DynamicModule { 54 | return { 55 | module: RmqModule, 56 | imports: [DiscoveryModule, ...imports], 57 | providers: [providerOptions, RmqService, MetaTagsScannerService, RmqErrorService].concat( 58 | providersExtended, 59 | ), 60 | exports: [RmqService], 61 | }; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@diy0r/nestjs-rabbitmq", 3 | "version": "0.28.0", 4 | "description": "Nestjs rabbitMQ module", 5 | "author": "Sobirov Diyorbek ", 6 | "private": false, 7 | "files": [ 8 | "./dist" 9 | ], 10 | "main": "./dist/index.js", 11 | "module": "./dist/index.js", 12 | "types": "./dist/index.d.ts", 13 | "license": "MIT", 14 | "scripts": { 15 | "build": "tsc -p tsconfig.build.json", 16 | "format": "prettier --write \"lib/**/*.ts\"", 17 | "lint:fix": "eslint \"{lib,test}/**/*.ts\" --fix", 18 | "lint": "eslint \"{lib,test}/**/*.ts\"", 19 | "prepare": "husky", 20 | "release": "release-it", 21 | "test": "jest" 22 | }, 23 | "dependencies": { 24 | "@types/amqplib": "^0.10.5", 25 | "amqplib": "^0.10.4", 26 | "class-validator": "^0.14.1", 27 | "reflect-metadata": "^0.2.0" 28 | }, 29 | "devDependencies": { 30 | "@commitlint/cli": "^19.3.0", 31 | "@commitlint/config-conventional": "^19.2.2", 32 | "@nestjs/cli": "^10.0.0", 33 | "@nestjs/common": "^10.0.0", 34 | "@nestjs/core": "^10.0.0", 35 | "@nestjs/schematics": "^10.0.0", 36 | "@nestjs/testing": "^10.0.0", 37 | "@types/jest": "^29.5.12", 38 | "@types/node": "^22.0.0", 39 | "@typescript-eslint/eslint-plugin": "^8.0.0", 40 | "@typescript-eslint/parser": "^8.0.0", 41 | "eslint": "^8.42.0", 42 | "eslint-config-prettier": "^10.1.1", 43 | "eslint-plugin-prettier": "^5.0.0", 44 | "husky": "^9.0.11", 45 | "jest": "^29.7.0", 46 | "prettier": "^3.3.2", 47 | "release-it": "^19.0.2", 48 | "rxjs": "^7.8.1", 49 | "ts-jest": "^29.1.5", 50 | "ts-loader": "^9.4.3", 51 | "ts-node": "^10.9.1", 52 | "tsconfig-paths": "^4.2.0", 53 | "typescript": "^5.5.2" 54 | }, 55 | "peerDependencies": { 56 | "@nestjs/common": "^10.0.0" 57 | }, 58 | "keywords": [ 59 | "RMQ", 60 | "Nest.js", 61 | "RabbitMQ", 62 | "amqplib", 63 | "amqplib-nestjs", 64 | "rabbitmq-nestjs", 65 | "nestjs-rmq", 66 | "nodejs" 67 | ], 68 | "repository": { 69 | "type": "git", 70 | "url": "https://github.com/DIY0R/nestjs-rabbitmq" 71 | }, 72 | "homepage": "https://github.com/DIY0R/nestjs-rabbitmq", 73 | "bugs": { 74 | "url": "https://github.com/DIY0R/nestjs-rabbitmq/issues" 75 | }, 76 | "npm": { 77 | "publish": true 78 | }, 79 | "publishConfig": { 80 | "access": "public" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/constants.ts: -------------------------------------------------------------------------------- 1 | import { INTERCEPTORS_METADATA } from '@nestjs/common/constants'; 2 | 3 | export const RMQ_OPTIONS = 'RMQ_Options'; 4 | export const RMQ_BROKER_OPTIONS = 'RMQ_BROKER_OPTIONS'; 5 | export const RMQ_MESSAGE_META_TAG = 'RMQ_MESSAGE_META_TAG'; 6 | export const RMQ_ROUTES_TRANSFORM = 'RMQ_ROUTES_TRANSFORM'; 7 | export const GET_INTERCEPTORS = 'GET_INTERCEPTORS'; 8 | export const TARGET_MODULE = 'TARGET_MODULE'; 9 | export const SER_DAS_KEY = 'SER_DAS_KEY'; 10 | export const RMQ_VALIDATE = 'RMQ_VALIDATE'; 11 | export const SERDES = 'SERDES'; 12 | export const INTERCEPTORS = 'INTERCEPTORS'; 13 | export const MIDDLEWARES = 'MIDDLEWARES'; 14 | export const INTERCEPTOR_CUSTOM_METADATA = INTERCEPTORS_METADATA; 15 | export const MIDDLEWARES_METADATA = 'MIDDLEWARE_KEY'; 16 | export const MESSAGE_ROUTER = 'MessageRouterExplorer'; 17 | export const MODULE_TOKEN = 'MODULE_UNIQ_TOKEN'; 18 | 19 | export const CLOSE_EVENT = 'close'; 20 | export const CONNECT_ERROR = 'error'; 21 | export const CONNECT_BLOCKED = 'blocked'; 22 | export const INITIALIZATION_STEP_DELAY = 400; 23 | export const TIMEOUT_INIT_INTERCEPTORS = 300; 24 | export const DEFAULT_TIMEOUT = 40000; 25 | export const RECONNECTION_INTERVAL = 5000; 26 | export const NON_ROUTE = 'There is no that route'; 27 | 28 | export const INDICATE_REPLY_QUEUE = 'Please indicate `replyToQueue`'; 29 | export const INDICATE_REPLY_QUEUE_GLOBAL = 'Please indicate `replyToQueue` in globalBroker'; 30 | export const TIMEOUT_ERROR = 'Response timeout error'; 31 | export const RECEIVED_MESSAGE_ERROR = 'Received a message but with an error'; 32 | export const ERROR_RMQ_SERVICE = 'RMQ service error'; 33 | export const NACKED = 'Negative acknowledgment'; 34 | export const RETURN_NOTHING = 'Route returned nothing'; 35 | export const NON_DECLARED_ROUTE = 'No Message Route has been declared in the Module'; 36 | 37 | export const ERROR_NO_ROUTE = "Requested service doesn't have a MessageRoute with this path"; 38 | export const EMPTY_OBJECT_MESSAGE = 'Received empty object message content'; 39 | export const MESSAGE_NON = 'Send an existing message'; 40 | export const CLOSE_MESSAGE = 'Disconnected from RMQ. Trying to reconnect'; 41 | export const CONNECT_FAILED_MESSAGE = 'Failed to connect to RMQ'; 42 | export const WRONG_CREDENTIALS_MESSAGE = 'Wrong credentials for RMQ'; 43 | export const CONNECT_BLOCKED_MESSAGE = 'Connection blocked'; 44 | export const SUCCESSFUL_CONNECT = 'Successfully connected to RabbitMQ'; 45 | export const ROOT_MODULE_DECLARED = 'Root RmqNestjsModule already declared!'; 46 | -------------------------------------------------------------------------------- /lib/common/error.class.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable } from '@nestjs/common'; 2 | import { Message, MessagePropertyHeaders } from 'amqplib'; 3 | import { RMQ_BROKER_OPTIONS, RMQ_OPTIONS } from '../constants'; 4 | import { IModuleBroker, IRmqErrorHeaders, IRMQOptions } from '../interfaces'; 5 | import { hostname } from 'os'; 6 | 7 | @Injectable() 8 | export class RmqErrorGlobalService { 9 | @Inject(RMQ_OPTIONS) private rmQoptions: IRMQOptions; 10 | 11 | public buildError(error: Error | RMQError) { 12 | if (!error) return null; 13 | let errorHeaders = {}; 14 | errorHeaders['-x-error'] = error.message; 15 | errorHeaders['-x-host'] = hostname(); 16 | errorHeaders['-x-service'] = (error as RMQError).service; 17 | if (this.isRMQError(error)) { 18 | errorHeaders = { 19 | ...errorHeaders, 20 | '-x-date': (error as RMQError).date, 21 | '-x-status-code': (error as RMQError).status, 22 | }; 23 | } 24 | return errorHeaders; 25 | } 26 | 27 | public errorHandler(msg: Message): any { 28 | const { headers } = msg.properties; 29 | const errorHandler = this.rmQoptions.extendedOptions?.globalBroker.replyTo.errorHandler; 30 | return errorHandler ? errorHandler.handle(headers) : RMQErrorHandler.handle(headers); 31 | } 32 | 33 | private isRMQError(error: Error | RMQError): boolean { 34 | return (error as RMQError).status !== undefined; 35 | } 36 | } 37 | @Injectable() 38 | export class RmqErrorService extends RmqErrorGlobalService { 39 | constructor(@Inject(RMQ_BROKER_OPTIONS) private readonly options: IModuleBroker) { 40 | super(); 41 | } 42 | 43 | public errorHandler(msg: Message): RMQError { 44 | const { headers } = msg.properties; 45 | const errorHandler = this.options.replyTo.errorHandler; 46 | return errorHandler ? errorHandler.handle(headers) : RMQErrorHandler.handle(headers); 47 | } 48 | } 49 | export class RMQError extends Error { 50 | message: string; 51 | service?: string; 52 | status?: number; 53 | date?: string; 54 | host?: string; 55 | constructor(message: string, service?: string, status?: number, host?: string, date?: string) { 56 | super(); 57 | Object.setPrototypeOf(this, new.target.prototype); 58 | this.message = message; 59 | this.date = date; 60 | this.status = status; 61 | this.host = host; 62 | this.service = service; 63 | } 64 | } 65 | 66 | export class RMQErrorHandler { 67 | public static handle(headers: IRmqErrorHeaders | MessagePropertyHeaders): Error | RMQError { 68 | return new RMQError( 69 | headers['-x-error'], 70 | headers['-x-service'], 71 | headers['-x-status-code'], 72 | headers['-x-host'], 73 | headers['-x-date'], 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/interfaces/rmq-options.interface.ts: -------------------------------------------------------------------------------- 1 | import { MessagePropertyHeaders, Options } from 'amqplib'; 2 | import { 3 | DynamicModule, 4 | ForwardReference, 5 | LoggerService, 6 | ModuleMetadata, 7 | Type, 8 | } from '@nestjs/common'; 9 | import { ISerDes } from './serdes.interface'; 10 | import { TypeRmqInterceptor } from './interceptor.interface'; 11 | import { TypeRmqMiddleware } from './middleware.interface'; 12 | import { IRmqErrorHeaders } from './error.headers.interface'; 13 | import { RMQErrorHandler } from '../common'; 14 | 15 | export interface IQueue { 16 | queue: string; 17 | options?: Options.AssertQueue; 18 | consumeOptions?: Options.Consume; 19 | } 20 | export interface IReplyQueue extends IQueue { 21 | errorHandler?: typeof RMQErrorHandler; 22 | } 23 | export enum TypeQueue { 24 | QUEUE, 25 | REPLY_QUEUE, 26 | } 27 | export enum TypeChannel { 28 | CHANNEL, 29 | CONFIRM_CHANNEL, 30 | } 31 | export interface IExchange { 32 | exchange: string; 33 | type: 'direct' | 'topic' | 'headers' | 'fanout' | 'match'; 34 | options?: Options.AssertExchange; 35 | } 36 | 37 | export type IRMQConnectConfig = string | Options.Connect; 38 | 39 | export interface IRMQOptions { 40 | connectOptions: IRMQConnectConfig; 41 | extendedOptions?: IRMQExtendedOptions; 42 | } 43 | 44 | export interface IRMQOptionsAsync { 45 | useFactory?: (...args: any[]) => Promise | IRMQOptions; 46 | inject?: any[]; 47 | imports?: ImportsType; 48 | } 49 | 50 | export interface IExtendedBroker { 51 | serDes?: ISerDes; 52 | interceptors?: TypeRmqInterceptor[]; 53 | middlewares?: TypeRmqMiddleware[]; 54 | } 55 | 56 | export interface IModuleBroker extends IExtendedBroker { 57 | exchange: IExchange; 58 | replyTo?: IReplyQueue; 59 | queue?: IQueue; 60 | messageTimeout?: number; 61 | serviceName?: string; 62 | } 63 | 64 | export interface IModuleBrokerAsync extends Pick, IExtendedBroker { 65 | useFactory?: (...args: any[]) => Promise | IModuleBroker; 66 | inject?: any[]; 67 | imports?: ImportsType; 68 | } 69 | 70 | export type ImportsType = Array< 71 | Type | DynamicModule | Promise | ForwardReference 72 | >; 73 | 74 | export interface IBindQueue { 75 | queue: string; 76 | source: string; 77 | pattern: string; 78 | args?: Record; 79 | } 80 | 81 | export interface ISendMessage { 82 | exchange: string; 83 | routingKey: string; 84 | content: Buffer; 85 | options: Options.Publish; 86 | } 87 | export interface IPublishOptions extends Options.Publish { 88 | timeout?: number; 89 | } 90 | 91 | export interface ISendToReplyQueueOptions { 92 | replyTo: string; 93 | content: Buffer; 94 | correlationId: string; 95 | headers: IRmqErrorHeaders | MessagePropertyHeaders; 96 | } 97 | 98 | export interface IAppOptions { 99 | logger?: LoggerService; 100 | logMessages: boolean; 101 | } 102 | 103 | export interface IGlobalBroker { 104 | replyTo: IReplyQueue; 105 | messageTimeout?: number; 106 | serviceName?: string; 107 | serDes?: ISerDes; 108 | errorsHandler?: typeof RMQErrorHandler; 109 | } 110 | 111 | export interface ISocketOptions { 112 | clientProperties?: { connection_name: string }; 113 | } 114 | 115 | interface ISocketOptionsCa extends ISocketOptions { 116 | passphrase?: string; 117 | ca?: (Buffer | string)[]; 118 | } 119 | 120 | export interface ISocketOptionsSSLPFX extends ISocketOptionsCa { 121 | pfx?: Buffer | string; 122 | } 123 | 124 | export interface ISocketOptionsSSLKEY extends ISocketOptionsCa { 125 | cert: Buffer | string; 126 | key: Buffer | string; 127 | } 128 | 129 | export interface IPrefetch { 130 | count: number; 131 | isGlobal: boolean; 132 | } 133 | 134 | export interface IRMQExtendedOptions { 135 | globalBroker?: IGlobalBroker; 136 | appOptions?: IAppOptions; 137 | prefetch?: IPrefetch; 138 | socketOptions?: ISocketOptionsSSLPFX | ISocketOptionsSSLKEY; 139 | typeChannel?: TypeChannel; 140 | } 141 | 142 | export interface INotifyReply { 143 | status: string; 144 | } 145 | -------------------------------------------------------------------------------- /test/mocks/rmq.event.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Logger } from '@nestjs/common'; 2 | import { ConsumeMessage } from 'amqplib'; 3 | import { 4 | RmqService, 5 | RmqMiddleware, 6 | MessageNonRoute, 7 | MessageRoute, 8 | RmqInterceptor, 9 | SerDes, 10 | RMQValidate, 11 | RMQError, 12 | } from '../../lib'; 13 | import { EventInterceptorClass, EventInterceptorEndpoint } from './event.interceptor'; 14 | import { 15 | EventMiddlewareClass, 16 | EventMiddlewareEndpoint, 17 | EventMiddlewareEndpointReturn, 18 | } from './event.middleware'; 19 | import { MyClass } from './dto/myClass.dto'; 20 | 21 | @Injectable() 22 | @SerDes({ 23 | deserialize: (message: Buffer): any => JSON.parse(message.toString()), 24 | serialize: (message: any): Buffer => Buffer.from(JSON.stringify(message)), 25 | }) 26 | @RmqMiddleware(EventMiddlewareClass) 27 | @RmqInterceptor(EventInterceptorClass) 28 | export class RmqEvents { 29 | constructor(private readonly rmqService: RmqService) {} 30 | @MessageRoute('text.text') 31 | receive(obj: any, consumeMessage: ConsumeMessage) { 32 | this.rmqService.ack(consumeMessage); 33 | return { message: obj }; 34 | } 35 | 36 | @MessageRoute('text.nothing') 37 | receiveReturnNoting(obj: any, consumeMessage: ConsumeMessage) { 38 | this.rmqService.ack(consumeMessage); 39 | } 40 | 41 | @MessageRoute('*.rpc.*') 42 | @SerDes({ 43 | deserialize: (message: Buffer): any => JSON.parse(message.toString()), 44 | serialize: (message: any): Buffer => Buffer.from(JSON.stringify(message)), 45 | }) 46 | receiveTopic(obj: any, consumeMessage: ConsumeMessage) { 47 | this.rmqService.ack(consumeMessage); 48 | return { message: obj }; 49 | } 50 | 51 | @MessageRoute('*.rpc.mix.#') 52 | receiveMixTopic(obj: any, consumeMessage: ConsumeMessage) { 53 | this.rmqService.ack(consumeMessage); 54 | return { message: obj }; 55 | } 56 | 57 | @MessageRoute('global.rpc') 58 | receiveGlobal(obj: any, consumeMessage: ConsumeMessage) { 59 | this.rmqService.ack(consumeMessage); 60 | return { message: obj }; 61 | } 62 | 63 | @MessageRoute('rpc.#') 64 | receiveTopicPattern(obj: any, consumeMessage: ConsumeMessage) { 65 | this.rmqService.ack(consumeMessage); 66 | return { message: obj }; 67 | } 68 | 69 | @MessageRoute('error.error') 70 | receiveTopicError(obj: any, consumeMessage: ConsumeMessage) { 71 | this.rmqService.ack(consumeMessage); 72 | throw new Error('error'); 73 | } 74 | 75 | @MessageRoute('error.error.rmq') 76 | receiveTopicErrorRmq(obj: any, consumeMessage: ConsumeMessage) { 77 | this.rmqService.ack(consumeMessage); 78 | throw new RMQError('error', 'myService', 302); 79 | } 80 | 81 | @MessageRoute('notify.global') 82 | receiveTopicNotify(obj: any, consumeMessage: ConsumeMessage) { 83 | this.rmqService.ack(consumeMessage); 84 | Logger.log(obj); 85 | } 86 | 87 | @MessageRoute('text.interceptor') 88 | @RmqInterceptor(EventInterceptorEndpoint) 89 | receiveMessage(obj: any, consumeMessage: ConsumeMessage) { 90 | this.rmqService.ack(consumeMessage); 91 | return obj; 92 | } 93 | 94 | @RmqMiddleware(EventMiddlewareEndpoint) 95 | @MessageRoute('text.middleware') 96 | messageMiddleware(obj: any, consumeMessage: ConsumeMessage) { 97 | this.rmqService.ack(consumeMessage); 98 | return obj; 99 | } 100 | 101 | @RmqMiddleware(EventMiddlewareEndpointReturn) 102 | @MessageRoute('text.middleware.return') 103 | messageMiddlewareReturn(obj: any, consumeMessage: ConsumeMessage) { 104 | this.rmqService.ack(consumeMessage); 105 | return obj; 106 | } 107 | 108 | @MessageRoute('text.number') 109 | numberGet(obj: any, consumeMessage: ConsumeMessage) { 110 | this.rmqService.ack(consumeMessage); 111 | return { number: obj.number }; 112 | } 113 | 114 | @MessageRoute('message.valid') 115 | @RMQValidate() 116 | getValidMessage(obj: MyClass, consumeMessage: ConsumeMessage) { 117 | this.rmqService.ack(consumeMessage); 118 | return { message: obj }; 119 | } 120 | 121 | @MessageNonRoute() 122 | receiveNonRoute(obj: any, consumeMessage: ConsumeMessage) { 123 | this.rmqService.ack(consumeMessage); 124 | return { message: obj }; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /lib/common/events.discovery.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, InjectionToken } from '@nestjs/common'; 2 | import { ModulesContainer, Reflector, MetadataScanner } from '@nestjs/core'; 3 | import { Module } from '@nestjs/core/injector/module'; 4 | import { Injectable as InjectableInterface } from '@nestjs/common/interfaces'; 5 | import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; 6 | import { 7 | INTERCEPTOR_CUSTOM_METADATA, 8 | MESSAGE_ROUTER, 9 | MIDDLEWARES_METADATA, 10 | MODULE_TOKEN, 11 | RMQ_VALIDATE, 12 | SER_DAS_KEY, 13 | } from '../constants'; 14 | import { 15 | CallbackFunctionVariadic, 16 | IMetaTagsMap, 17 | IRmqInterceptor, 18 | ISerDes, 19 | TypeRmqInterceptor, 20 | TypeRmqMiddleware, 21 | } from '../interfaces'; 22 | import { RQMColorLogger } from './logger'; 23 | 24 | @Injectable() 25 | export class MetaTagsScannerService { 26 | private logger = new RQMColorLogger(false); 27 | constructor( 28 | private readonly metadataScanner: MetadataScanner, 29 | private readonly reflector: Reflector, 30 | private readonly modulesContainer: ModulesContainer, 31 | ) {} 32 | 33 | public findModulesByProviderValue(tokenValue: string): Module { 34 | for (const module of this.modulesContainer.values()) { 35 | for (const importedModule of module.imports.values()) { 36 | const provider = importedModule.providers.get(MODULE_TOKEN); 37 | if (provider && provider.instance === tokenValue) return module; 38 | } 39 | } 40 | return null; 41 | } 42 | 43 | public scan(metaTag: string, tokenValue: string) { 44 | const rmqMessagesMap: IMetaTagsMap = new Map(); 45 | const currentModule = this.findModulesByProviderValue(tokenValue); 46 | if (!currentModule) return rmqMessagesMap; 47 | const providersAndControllers = this.getProvidersAndControllers(currentModule); 48 | providersAndControllers.forEach((provider: InstanceWrapper) => { 49 | const { instance } = provider; 50 | if (instance instanceof Object) { 51 | const allMethodNames = this.metadataScanner.getAllMethodNames(instance); 52 | allMethodNames.forEach((name: string) => 53 | this.lookupMethods(metaTag, rmqMessagesMap, instance, name, currentModule.injectables), 54 | ); 55 | } 56 | }); 57 | return rmqMessagesMap; 58 | } 59 | 60 | private getProvidersAndControllers(module: Module) { 61 | return [...module.providers.values(), ...module.controllers.values()]; 62 | } 63 | 64 | private lookupMethods( 65 | metaTag: string, 66 | rmqMessagesMap: IMetaTagsMap, 67 | instance: object, 68 | methodName: string, 69 | injectables: Map>, 70 | ) { 71 | const method = instance[methodName]; 72 | const event = this.getMetaData(metaTag, method); 73 | if (event) { 74 | const boundHandler = method.bind(instance); 75 | const serdes = this.getSerDesMetaData(method, instance.constructor); 76 | const middlewares = this.getLinesMetaDate( 77 | method, 78 | instance.constructor, 79 | MIDDLEWARES_METADATA, 80 | ); 81 | const interceptors = this.getInterceptors(injectables, method, instance.constructor); 82 | const validate = this.getValidation(instance, method); 83 | rmqMessagesMap.set(event, { 84 | handler: boundHandler, 85 | serdes, 86 | interceptors, 87 | middlewares, 88 | validate, 89 | }); 90 | this.logger.log('Mapped ' + event, MESSAGE_ROUTER); 91 | } 92 | } 93 | 94 | private getSerDesMetaData(method: CallbackFunctionVariadic, target: object) { 95 | return ( 96 | this.getMetaData(SER_DAS_KEY, method) || 97 | this.getMetaData(SER_DAS_KEY, target) 98 | ); 99 | } 100 | 101 | private getLinesMetaDate( 102 | method: CallbackFunctionVariadic, 103 | classProvider: Record, 104 | key: string, 105 | ): T[] { 106 | const methodMeta = this.getMetaData(key, method); 107 | const targetMeta = this.getMetaData(key, classProvider); 108 | return [targetMeta, methodMeta].filter(meta => meta !== undefined); 109 | } 110 | 111 | private getInterceptors( 112 | injectables: Map, 113 | method: CallbackFunctionVariadic, 114 | classProvider: Record, 115 | ) { 116 | const interceptors = this.getLinesMetaDate( 117 | method, 118 | classProvider, 119 | INTERCEPTOR_CUSTOM_METADATA, 120 | ); 121 | return interceptors.map(interceptor => { 122 | const instance: IRmqInterceptor = injectables.get(interceptor[0]).instance; 123 | return instance.intercept.bind(instance); 124 | }); 125 | } 126 | 127 | private getValidation(instance: Record, method: CallbackFunctionVariadic) { 128 | const validator = this.reflector.get(RMQ_VALIDATE, method); 129 | if (!validator) return null; 130 | const paramTypes = Reflect.getMetadata( 131 | 'design:paramtypes', 132 | Object.getPrototypeOf(instance), 133 | method.name, 134 | ); 135 | return paramTypes[0]; 136 | } 137 | 138 | private getMetaData(key: string, target: any) { 139 | return this.reflector.get(key, target); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /lib/rmq.global.service.ts: -------------------------------------------------------------------------------- 1 | import { Inject, LoggerService, OnModuleInit } from '@nestjs/common'; 2 | import { ConsumeMessage, Message, Replies, Channel, Options, ConfirmChannel } from 'amqplib'; 3 | import { EventEmitter } from 'stream'; 4 | import { RmqNestjsConnectService } from './rmq-connect.service'; 5 | import { 6 | IRMQExtendedOptions, 7 | INotifyReply, 8 | IPublishOptions, 9 | ISerDes, 10 | TypeChannel, 11 | TypeQueue, 12 | IRMQOptions, 13 | } from './interfaces'; 14 | 15 | import { 16 | DEFAULT_TIMEOUT, 17 | INDICATE_REPLY_QUEUE_GLOBAL, 18 | INITIALIZATION_STEP_DELAY, 19 | NACKED, 20 | RMQ_OPTIONS, 21 | TIMEOUT_ERROR, 22 | } from './constants'; 23 | 24 | import { 25 | getUniqId, 26 | RMQError, 27 | RmqErrorGlobalService, 28 | RQMColorLogger, 29 | defaultSerDes, 30 | } from './common'; 31 | 32 | export class RmqGlobalService implements OnModuleInit { 33 | private replyToQueue: Replies.AssertQueue = null; 34 | private extendedOptions: IRMQExtendedOptions = null; 35 | private serDes: ISerDes = defaultSerDes; 36 | private sendResponseEmitter: EventEmitter = new EventEmitter(); 37 | private logger: LoggerService; 38 | private isInitialized = false; 39 | 40 | constructor( 41 | @Inject(RMQ_OPTIONS) private readonly RMQOptions: IRMQOptions, 42 | private readonly rmqNestjsConnectService: RmqNestjsConnectService, 43 | private readonly rmqErrorGlobalService: RmqErrorGlobalService, 44 | ) { 45 | this.extendedOptions = RMQOptions.extendedOptions ?? {}; 46 | this.serDes = this.extendedOptions?.globalBroker?.serDes ?? defaultSerDes; 47 | this.logger = RMQOptions.extendedOptions?.appOptions?.logger 48 | ? RMQOptions.extendedOptions.appOptions.logger 49 | : new RQMColorLogger(this.extendedOptions?.appOptions?.logMessages); 50 | } 51 | 52 | async onModuleInit() { 53 | if (this.extendedOptions?.globalBroker?.replyTo) await this.replyQueue(); 54 | this.isInitialized = true; 55 | } 56 | 57 | get channel(): Promise { 58 | return this.rmqNestjsConnectService.getBaseChannel(); 59 | } 60 | 61 | public async send( 62 | exchange: string, 63 | topic: string, 64 | message: IMessage, 65 | options?: IPublishOptions, 66 | ): Promise { 67 | try { 68 | if (!this.replyToQueue) throw Error(INDICATE_REPLY_QUEUE_GLOBAL); 69 | await this.initializationCheck(); 70 | const { messageTimeout, serviceName } = this.extendedOptions.globalBroker; 71 | return new Promise(async (resolve, reject) => { 72 | const correlationId = getUniqId(); 73 | const timeout = options?.timeout ?? messageTimeout ?? DEFAULT_TIMEOUT; 74 | const timerId = setTimeout(() => reject(TIMEOUT_ERROR), timeout); 75 | this.sendResponseEmitter.once(correlationId, (msg: Message) => { 76 | clearTimeout(timerId); 77 | if (msg.properties?.headers?.['-x-error']) { 78 | return reject(this.rmqErrorGlobalService.errorHandler(msg)); 79 | } 80 | const content = msg.content; 81 | if (content.toString()) resolve(this.serDes.deserialize(content)); 82 | }); 83 | const confirmationFunction = (err: any, ok: Replies.Empty) => { 84 | if (err) { 85 | clearTimeout(timerId); 86 | reject(new RMQError(NACKED)); 87 | } 88 | }; 89 | 90 | await this.rmqNestjsConnectService.publish( 91 | { 92 | exchange: exchange, 93 | routingKey: topic, 94 | content: this.serDes.serialize(message), 95 | options: { 96 | replyTo: this.replyToQueue.queue, 97 | appId: serviceName, 98 | correlationId, 99 | timestamp: new Date().getTime(), 100 | ...options, 101 | }, 102 | }, 103 | confirmationFunction, 104 | ); 105 | }); 106 | } catch (error) { 107 | this.logger.error(error); 108 | } 109 | } 110 | 111 | public notify( 112 | exchange: string, 113 | topic: string, 114 | message: IMessage, 115 | options?: Options.Publish, 116 | ): Promise { 117 | return new Promise((resolve, reject) => { 118 | const confirmationFunction = (err: any, ok: Replies.Empty) => { 119 | if (err !== null) return reject(NACKED); 120 | resolve({ status: 'ok' }); 121 | }; 122 | this.rmqNestjsConnectService.publish( 123 | { 124 | exchange, 125 | routingKey: topic, 126 | content: this.serDes.serialize(message), 127 | options: { 128 | appId: this.extendedOptions?.globalBroker?.serviceName ?? '', 129 | timestamp: new Date().getTime(), 130 | ...options, 131 | }, 132 | }, 133 | confirmationFunction, 134 | ); 135 | if (this.extendedOptions?.typeChannel !== TypeChannel.CONFIRM_CHANNEL) { 136 | resolve({ status: 'ok' }); 137 | } 138 | }); 139 | } 140 | 141 | public async sendToQueue( 142 | queue: string, 143 | content: IMessage, 144 | options?: Options.Publish, 145 | ): Promise { 146 | const status = await this.rmqNestjsConnectService.sendToQueue( 147 | queue, 148 | this.serDes.serialize(content), 149 | options, 150 | ); 151 | return status; 152 | } 153 | 154 | public ack(...params: Parameters): ReturnType { 155 | return this.rmqNestjsConnectService.ack(...params); 156 | } 157 | 158 | private async listenReplyQueue(message: ConsumeMessage | null): Promise { 159 | if (message.properties.correlationId) { 160 | this.sendResponseEmitter.emit(message.properties.correlationId, message); 161 | } 162 | } 163 | 164 | private async replyQueue() { 165 | this.replyToQueue = await this.rmqNestjsConnectService.assertQueue( 166 | TypeQueue.REPLY_QUEUE, 167 | this.extendedOptions.globalBroker.replyTo, 168 | ); 169 | await this.rmqNestjsConnectService.listenReplyQueue( 170 | this.replyToQueue.queue, 171 | this.listenReplyQueue.bind(this), 172 | this.extendedOptions.globalBroker.replyTo.consumeOptions, 173 | ); 174 | } 175 | 176 | private async initializationCheck() { 177 | if (this.isInitialized) return; 178 | await new Promise(resolve => setTimeout(resolve, INITIALIZATION_STEP_DELAY)); 179 | await this.initializationCheck(); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /test/rmq-nestjs.spec.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { Test } from '@nestjs/testing'; 3 | import { RmqServiceController } from './mocks/rmq.controller'; 4 | import { RmqModule, RmqService, TypeChannel, RMQError } from '../lib'; 5 | import { ConnectionMockModule } from './mocks/rmq-nestjs.module'; 6 | import { hostname } from 'node:os'; 7 | import { MyGlobalRMQErrorHandler } from './mocks/error.handlers'; 8 | import { MyClass } from './mocks/dto/myClass.dto'; 9 | 10 | describe('RMQe2e', () => { 11 | let api: INestApplication; 12 | let rmqServiceController: RmqServiceController; 13 | let rmqService: RmqService; 14 | 15 | beforeAll(async () => { 16 | const apiModule = await Test.createTestingModule({ 17 | imports: [ 18 | RmqModule.forRootAsync({ 19 | useFactory: async () => ({ 20 | connectOptions: { 21 | username: 'for-test', 22 | password: 'for-test', 23 | hostname: 'localhost', 24 | port: 5672, 25 | vhost: 'local', 26 | protocol: 'amqp', 27 | }, 28 | extendedOptions: { 29 | typeChannel: TypeChannel.CONFIRM_CHANNEL, 30 | globalBroker: { 31 | replyTo: { 32 | queue: '', 33 | options: { exclusive: true }, 34 | consumeOptions: { noAck: true }, 35 | errorHandler: MyGlobalRMQErrorHandler, 36 | }, 37 | messageTimeout: 50000, 38 | serviceName: 'global service', 39 | serDes: { 40 | deserialize: (message: Buffer): any => JSON.parse(message.toString()), 41 | serialize: (message: any): Buffer => Buffer.from(JSON.stringify(message)), 42 | }, 43 | }, 44 | socketOptions: { 45 | clientProperties: { connection_name: 'myFriendlyName' }, 46 | }, 47 | }, 48 | }), 49 | inject: [], 50 | }), 51 | ConnectionMockModule, 52 | ], 53 | }).compile(); 54 | 55 | api = apiModule.createNestApplication(); 56 | await api.init(); 57 | 58 | rmqServiceController = apiModule.get(RmqServiceController); 59 | rmqService = apiModule.get(RmqService); 60 | }); 61 | 62 | test('check connection', async () => { 63 | const isConnected = rmqService.healthCheck(); 64 | expect(isConnected).toBe(true); 65 | }); 66 | 67 | describe('notify', () => { 68 | it('successful global notify()', async () => { 69 | const obj = { time: '001', fulled: 12 }; 70 | const response = await rmqServiceController.sendNotify(obj); 71 | expect(response).toEqual({ status: 'ok' }); 72 | }); 73 | 74 | it('successful service notify()', async () => { 75 | const obj = { time: '001', fulled: 12 }; 76 | const response = await rmqServiceController.sendNotifyService(obj); 77 | expect(response).toEqual({ status: 'ok' }); 78 | }); 79 | }); 80 | 81 | describe('rpc exchange', () => { 82 | it('successful global send()', async () => { 83 | const obj = { time: '001', fulled: 12 }; 84 | const { message } = await rmqServiceController.sendGlobal(obj, 'global.rpc'); 85 | expect(message).toEqual(obj); 86 | }); 87 | 88 | it('successful send()', async () => { 89 | const obj = { obj: 1 }; 90 | const topic = 'text.text'; 91 | const { message } = await rmqServiceController.sendMessage(obj, topic); 92 | expect(message).toEqual(obj); 93 | }); 94 | 95 | it('successful send() but return nothing', async () => { 96 | const obj = { obj: 1 }; 97 | const topic = 'text.nothing'; 98 | const message = await rmqServiceController.sendMessage(obj, topic); 99 | expect(message).toEqual({}); 100 | }); 101 | 102 | it('send topic pattern #1 "*"', async () => { 103 | const obj = { time: 1 }; 104 | const topic = 'message.rpc.tsp'; 105 | const { message } = await rmqServiceController.sendMessage(obj, topic); 106 | expect(message).toEqual(obj); 107 | }); 108 | 109 | it('send topic pattern #2 "#"', async () => { 110 | const obj = { time: 1 }; 111 | const topic = 'rpc.text.text'; 112 | const { message } = await rmqServiceController.sendMessage(obj, topic); 113 | expect(message).toEqual(obj); 114 | }); 115 | 116 | it('send mix topic pattern #3 "*#"', async () => { 117 | const obj = { time: 1 }; 118 | const topic = 'text.rpc.mix.pool.too'; 119 | const { message } = await rmqServiceController.sendMessage(obj, topic); 120 | expect(message).toEqual(obj); 121 | }); 122 | 123 | it('error route with Error', async () => { 124 | try { 125 | const obj = { time: 1 }; 126 | const topic = 'error.error'; 127 | await rmqServiceController.sendMessage(obj, topic); 128 | } catch (error) { 129 | expect((error as RMQError).host).toBe(hostname()); 130 | } 131 | }); 132 | 133 | it('error route with rmqError', async () => { 134 | try { 135 | const obj = { time: 1 }; 136 | const topic = 'error.error.rmq'; 137 | await rmqServiceController.sendMessage(obj, topic); 138 | } catch (error) { 139 | expect((error as RMQError).status).toBe(302); 140 | expect((error as RMQError).date).toBe(new Date().getMonth().toString()); 141 | } 142 | }); 143 | 144 | it('error route with rmqError global', async () => { 145 | try { 146 | const obj = { time: 1 }; 147 | const topic = 'error.error.rmq'; 148 | await rmqServiceController.sendGlobal(obj, topic); 149 | } catch (error) { 150 | expect((error as RMQError).status).toBe(302); 151 | expect((error as RMQError).date).toBe(new Date().getDate().toString()); 152 | } 153 | }); 154 | }); 155 | 156 | describe('send message to queue', () => { 157 | it('send to Queue', async () => { 158 | const obj = { aq: 1121 }; 159 | const status = await rmqServiceController.sendToQueue('test-for', obj); 160 | expect(status).toBeTruthy(); 161 | }); 162 | }); 163 | 164 | describe('send message with Providers', () => { 165 | it('send with interceptors', async () => { 166 | const obj = { arrayInterceptor: [0] }; 167 | const topic = 'text.interceptor'; 168 | const message = await rmqServiceController.sendMessageWithProvider<{ 169 | arrayInterceptor: number[]; 170 | }>(obj, topic); 171 | expect(message.arrayInterceptor).toEqual([0, 1, 2, 3, 4, 5, 6]); 172 | }); 173 | 174 | it('send with middleware', async () => { 175 | const obj = { arrayMiddleware: [0] }; 176 | const topic = 'text.middleware'; 177 | const message = await rmqServiceController.sendMessageWithProvider<{ 178 | arrayMiddleware: number[]; 179 | }>(obj, topic); 180 | expect(message.arrayMiddleware).toEqual([0, 1, 2, 3]); 181 | }); 182 | 183 | it('send with middleware that returned', async () => { 184 | const obj = {}; 185 | const topic = 'text.middleware.return'; 186 | const message = await rmqServiceController.sendMessageWithProvider<{ 187 | return: boolean; 188 | }>(obj, topic); 189 | expect(message).toEqual({ return: true }); 190 | }); 191 | }); 192 | 193 | describe('validation messages', () => { 194 | it('send valid message', async () => { 195 | const topic = 'message.valid'; 196 | const objMessage: MyClass = { 197 | age: 20, 198 | name: 'Diy0r', 199 | }; 200 | const { message } = await rmqServiceController.sendMessage(objMessage, topic); 201 | expect(message).toEqual(message); 202 | }); 203 | 204 | it('send invalid message', async () => { 205 | try { 206 | const topic = 'message.valid'; 207 | const objMessage = { 208 | age: '20', 209 | name: 'FooLii1p', 210 | }; 211 | await rmqServiceController.sendMessage(objMessage, topic); 212 | } catch (error) { 213 | expect(error.message).toEqual( 214 | 'The name must be less than 5; age must be an integer number', 215 | ); 216 | } 217 | }); 218 | }); 219 | 220 | afterAll(async () => { 221 | await delay(500); 222 | await api.close(); 223 | }); 224 | }); 225 | 226 | async function delay(time: number): Promise { 227 | return new Promise(resolve => { 228 | setTimeout(() => { 229 | resolve(); 230 | }, time); 231 | }); 232 | } 233 | -------------------------------------------------------------------------------- /lib/rmq-connect.service.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable, LoggerService, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; 2 | import { 3 | CLOSE_EVENT, 4 | CLOSE_MESSAGE, 5 | CONNECT_BLOCKED, 6 | CONNECT_BLOCKED_MESSAGE, 7 | CONNECT_ERROR, 8 | CONNECT_FAILED_MESSAGE, 9 | INITIALIZATION_STEP_DELAY, 10 | RECONNECTION_INTERVAL, 11 | RMQ_OPTIONS, 12 | ROOT_MODULE_DECLARED, 13 | SUCCESSFUL_CONNECT, 14 | } from './constants'; 15 | import { 16 | Channel, 17 | ConfirmChannel, 18 | Connection, 19 | ConsumeMessage, 20 | Options, 21 | Replies, 22 | connect, 23 | } from 'amqplib'; 24 | import { 25 | IRMQConnectConfig, 26 | IExchange, 27 | IQueue, 28 | TypeQueue, 29 | IBindQueue, 30 | ISendMessage, 31 | ISendToReplyQueueOptions, 32 | TypeChannel, 33 | IRMQOptions, 34 | IPrefetch, 35 | } from './interfaces'; 36 | 37 | import { RQMColorLogger } from './common'; 38 | 39 | @Injectable() 40 | export class RmqNestjsConnectService implements OnModuleInit, OnModuleDestroy { 41 | private connection: Connection = null; 42 | private baseChannel: Channel | ConfirmChannel = null; 43 | private replyToChannel: Channel = null; 44 | public isConnected = false; 45 | private logger: LoggerService; 46 | private isInitialized = false; 47 | constructor( 48 | @Inject(RMQ_OPTIONS) 49 | private readonly RMQOptions: IRMQOptions, 50 | ) { 51 | this.logger = RMQOptions.extendedOptions?.appOptions?.logger 52 | ? RMQOptions.extendedOptions.appOptions.logger 53 | : new RQMColorLogger(RMQOptions.extendedOptions?.appOptions?.logMessages); 54 | } 55 | 56 | async onModuleInit(): Promise { 57 | if (this.isInitialized) throw Error(ROOT_MODULE_DECLARED); 58 | await this.setUpConnect(this.RMQOptions.connectOptions); 59 | await this.setUpChannels(); 60 | this.isInitialized = true; 61 | } 62 | 63 | async assertExchange(options: IExchange): Promise { 64 | try { 65 | await this.initializationCheck(); 66 | const exchange = await this.baseChannel.assertExchange( 67 | options.exchange, 68 | options.type, 69 | options.options, 70 | ); 71 | return exchange; 72 | } catch (error) { 73 | throw new Error(`Failed to assert exchange '${options.exchange}': ${error.message}`); 74 | } 75 | } 76 | 77 | ack(...params: Parameters): ReturnType { 78 | return this.baseChannel.ack(...params); 79 | } 80 | 81 | nack(...params: Parameters): ReturnType { 82 | return this.baseChannel.nack(...params); 83 | } 84 | 85 | async assertQueue(typeQueue: TypeQueue, options?: IQueue): Promise { 86 | await this.initializationCheck(); 87 | try { 88 | if (typeQueue == TypeQueue.QUEUE) { 89 | return this.baseChannel.assertQueue(options.queue, options.options); 90 | } 91 | return await this.replyToChannel.assertQueue(options.queue, options.options); 92 | } catch (error) { 93 | throw new Error(`Failed to assert ${typeQueue} queue: ${error}`); 94 | } 95 | } 96 | 97 | async getBaseChannel() { 98 | await this.initializationCheck(); 99 | return this.baseChannel; 100 | } 101 | 102 | async bindQueue(bindQueue: IBindQueue): Promise { 103 | await this.initializationCheck(); 104 | try { 105 | await this.baseChannel.bindQueue( 106 | bindQueue.queue, 107 | bindQueue.source, 108 | bindQueue.pattern, 109 | bindQueue.args, 110 | ); 111 | } catch (error) { 112 | throw new Error(`Failed to Bind Queue ,source:${bindQueue.source} queue: ${bindQueue.queue}`); 113 | } 114 | } 115 | 116 | async sendToReplyQueue(sendToQueueOptions: ISendToReplyQueueOptions) { 117 | try { 118 | await this.initializationCheck(); 119 | this.replyToChannel.sendToQueue(sendToQueueOptions.replyTo, sendToQueueOptions.content, { 120 | correlationId: sendToQueueOptions.correlationId, 121 | headers: sendToQueueOptions.headers, 122 | }); 123 | } catch (error) { 124 | throw new Error(`Failed to send Reply Queue`); 125 | } 126 | } 127 | 128 | async listenReplyQueue( 129 | queue: string, 130 | listenQueue: (msg: ConsumeMessage | null) => void, 131 | consumeOptions?: Options.Consume, 132 | ) { 133 | try { 134 | await this.replyToChannel.consume( 135 | queue, 136 | listenQueue, 137 | consumeOptions || { 138 | noAck: true, 139 | }, 140 | ); 141 | } catch (error) { 142 | throw new Error(`Failed to send listen Reply Queue`); 143 | } 144 | } 145 | 146 | async listenQueue( 147 | queue: string, 148 | listenQueue: (msg: ConsumeMessage | null) => void, 149 | consumeOptions?: Options.Consume, 150 | ): Promise { 151 | try { 152 | await this.baseChannel.consume( 153 | queue, 154 | listenQueue, 155 | consumeOptions || { 156 | noAck: false, 157 | }, 158 | ); 159 | } catch (error) { 160 | throw new Error(`Failed to listen Queue`); 161 | } 162 | } 163 | 164 | async publish( 165 | sendMessage: ISendMessage, 166 | confirmationFunction?: (err: any, ok: Replies.Empty) => void, 167 | ): Promise { 168 | try { 169 | await this.initializationCheck(); 170 | this.baseChannel.publish( 171 | sendMessage.exchange, 172 | sendMessage.routingKey, 173 | sendMessage.content, 174 | { 175 | replyTo: sendMessage.options.replyTo, 176 | correlationId: sendMessage.options.correlationId, 177 | }, 178 | confirmationFunction, 179 | ); 180 | } catch (error) { 181 | throw new Error(`Failed to send message ${error}`); 182 | } 183 | } 184 | 185 | private async setUpConnect(connectOptions: IRMQConnectConfig): Promise { 186 | try { 187 | this.connection = await connect( 188 | connectOptions, 189 | this.RMQOptions.extendedOptions?.socketOptions, 190 | ); 191 | this.isConnected = true; 192 | this.logger.log(SUCCESSFUL_CONNECT); 193 | this.connection.on(CLOSE_EVENT, err => { 194 | this.isConnected = false; 195 | this.logger.error(`${CLOSE_MESSAGE}: ${err.message}`); 196 | this.reconnect(connectOptions); 197 | }); 198 | 199 | this.connection.on(CONNECT_ERROR, err => { 200 | this.logger.error(`${CONNECT_FAILED_MESSAGE}: ${err.message}`); 201 | }); 202 | this.connection.on(CONNECT_BLOCKED, err => { 203 | this.logger.error(`${CONNECT_BLOCKED_MESSAGE}: ${err.message}`); 204 | }); 205 | } catch (err) { 206 | this.logger.error(`Failed to connect: ${err.message}`); 207 | } 208 | } 209 | 210 | private async reconnect(options: IRMQConnectConfig): Promise { 211 | this.logger.log('Attempting to reconnect...'); 212 | setTimeout(async () => { 213 | try { 214 | await this.setUpConnect(options); 215 | } catch (err) { 216 | this.logger.error(`Reconnection failed: ${err.message}`); 217 | this.reconnect(options); 218 | } 219 | }, RECONNECTION_INTERVAL); 220 | } 221 | 222 | private async setUpChannels() { 223 | try { 224 | const { extendedOptions } = this.RMQOptions; 225 | const isConfirmChannel = extendedOptions?.typeChannel === TypeChannel.CONFIRM_CHANNEL; 226 | this.baseChannel = isConfirmChannel 227 | ? await this.createConfirmChannel() 228 | : await this.createChannel(); 229 | this.replyToChannel = await this.createChannel(); 230 | if (extendedOptions?.prefetch) await this.prefetch(extendedOptions.prefetch); 231 | } catch (error) { 232 | console.error('Error setting up channels:', error); 233 | throw error; 234 | } 235 | } 236 | 237 | private prefetch(prefetch: IPrefetch): Promise { 238 | return this.baseChannel.prefetch(prefetch.count, prefetch.isGlobal); 239 | } 240 | 241 | async sendToQueue(queue: string, content: Buffer, options?: Options.Publish): Promise { 242 | try { 243 | await this.initializationCheck(); 244 | return this.baseChannel.sendToQueue(queue, content, options); 245 | } catch (error) { 246 | throw new Error(`Failed to send message ${error}`); 247 | } 248 | } 249 | 250 | private async initializationCheck() { 251 | if (this.isInitialized) return; 252 | await new Promise(resolve => setTimeout(resolve, INITIALIZATION_STEP_DELAY)); 253 | await this.initializationCheck(); 254 | } 255 | 256 | private async createChannel() { 257 | return await this.connection.createChannel(); 258 | } 259 | 260 | private async createConfirmChannel() { 261 | return await this.connection.createConfirmChannel(); 262 | } 263 | 264 | async onModuleDestroy(): Promise { 265 | await this.baseChannel.close(); 266 | await this.replyToChannel.close(); 267 | await this.connection.close(); 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /lib/rmq.service.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable, LoggerService, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; 2 | import { 3 | IRMQExtendedOptions, 4 | INotifyReply, 5 | IPublishOptions, 6 | IResult, 7 | IRmqMiddleware, 8 | ISerDes, 9 | ReverseFunction, 10 | TypeChannel, 11 | TypeQueue, 12 | TypeRmqInterceptor, 13 | TypeRmqMiddleware, 14 | IRMQOptions, 15 | IModuleBroker, 16 | CallbackFunctionVariadic, 17 | IConsumeFunction, 18 | IMetaTagsMap, 19 | MetaTagEndpoint, 20 | } from './interfaces'; 21 | import { 22 | DEFAULT_TIMEOUT, 23 | INDICATE_REPLY_QUEUE, 24 | INITIALIZATION_STEP_DELAY, 25 | INTERCEPTORS, 26 | MIDDLEWARES, 27 | MODULE_TOKEN, 28 | NACKED, 29 | NON_ROUTE, 30 | RMQ_BROKER_OPTIONS, 31 | RMQ_MESSAGE_META_TAG, 32 | SERDES, 33 | TIMEOUT_ERROR, 34 | NON_DECLARED_ROUTE, 35 | RMQ_OPTIONS, 36 | } from './constants'; 37 | import { ConsumeMessage, Message, Replies, Channel, Options } from 'amqplib'; 38 | import { MetaTagsScannerService, RMQError, RmqErrorService, toRegex } from './common'; 39 | import { RmqNestjsConnectService } from './rmq-connect.service'; 40 | import { getUniqId } from './common/get-uniqId'; 41 | import { EventEmitter } from 'stream'; 42 | import { RQMColorLogger } from './common/logger'; 43 | import { ModuleRef } from '@nestjs/core'; 44 | import { validate } from 'class-validator'; 45 | 46 | @Injectable() 47 | export class RmqService implements OnModuleInit, OnModuleDestroy { 48 | private sendResponseEmitter: EventEmitter = new EventEmitter(); 49 | private extendedOptions: IRMQExtendedOptions = null; 50 | private rmqMessageTags: IMetaTagsMap = null; 51 | private replyToQueue: Replies.AssertQueue = null; 52 | private exchange: Replies.AssertExchange = null; 53 | private isInitialized: boolean = false; 54 | private connected = false; 55 | private logger: LoggerService; 56 | constructor( 57 | private readonly moduleRef: ModuleRef, 58 | private readonly rmqNestjsConnectService: RmqNestjsConnectService, 59 | private readonly metaTagsScannerService: MetaTagsScannerService, 60 | private readonly rmqErrorService: RmqErrorService, 61 | @Inject(RMQ_OPTIONS) 62 | private readonly RMQOptions: IRMQOptions, 63 | @Inject(RMQ_BROKER_OPTIONS) private readonly options: IModuleBroker, 64 | @Inject(SERDES) private readonly serDes: ISerDes, 65 | @Inject(INTERCEPTORS) private readonly interceptors: TypeRmqInterceptor[], 66 | @Inject(MIDDLEWARES) private readonly middlewares: TypeRmqMiddleware[], 67 | @Inject(MODULE_TOKEN) private readonly moduleToken: string, 68 | ) { 69 | this.extendedOptions = RMQOptions.extendedOptions ?? {}; 70 | this.logger = RMQOptions.extendedOptions?.appOptions?.logger 71 | ? RMQOptions.extendedOptions.appOptions?.logger 72 | : new RQMColorLogger(this.extendedOptions.appOptions?.logMessages); 73 | } 74 | 75 | async onModuleInit() { 76 | await this.init(); 77 | this.isInitialized = true; 78 | } 79 | 80 | public healthCheck() { 81 | return this.rmqNestjsConnectService.isConnected; 82 | } 83 | 84 | private async init() { 85 | this.exchange = await this.rmqNestjsConnectService.assertExchange(this.options.exchange); 86 | if (this.options?.replyTo) await this.assertReplyQueue(); 87 | if (this.options?.queue) { 88 | this.scanMetaTags(); 89 | await this.bindQueueExchange(); 90 | } 91 | } 92 | 93 | public async send( 94 | topic: string, 95 | message: IMessage, 96 | options?: IPublishOptions, 97 | ): Promise { 98 | if (!this.replyToQueue) throw Error(INDICATE_REPLY_QUEUE); 99 | await this.initializationCheck(); 100 | const correlationId = getUniqId(); 101 | const timeout = options?.timeout ?? this.options.messageTimeout ?? DEFAULT_TIMEOUT; 102 | return new Promise(async (resolve, reject) => { 103 | const timerId = setTimeout(() => { 104 | reject(new RMQError(TIMEOUT_ERROR)); 105 | }, timeout); 106 | this.sendResponseEmitter.once(correlationId, (msg: Message) => { 107 | clearTimeout(timerId); 108 | if (msg.properties?.headers?.['-x-error']) { 109 | return reject(this.rmqErrorService.errorHandler(msg)); 110 | } 111 | const content = msg.content; 112 | if (content.toString()) resolve(this.serDes.deserialize(content)); 113 | }); 114 | const confirmationFunction = (err: any, ok: Replies.Empty) => { 115 | if (err) { 116 | clearTimeout(timerId); 117 | reject(new RMQError(NACKED)); 118 | } 119 | }; 120 | await this.rmqNestjsConnectService.publish( 121 | { 122 | exchange: this.options.exchange.exchange, 123 | routingKey: topic, 124 | content: this.serDes.serialize(message), 125 | options: { 126 | replyTo: this.replyToQueue.queue, 127 | appId: this.options.serviceName, 128 | correlationId, 129 | timestamp: new Date().getTime(), 130 | ...options, 131 | }, 132 | }, 133 | confirmationFunction, 134 | ); 135 | }); 136 | } 137 | 138 | public async notify( 139 | topic: string, 140 | message: IMessage, 141 | options?: Options.Publish, 142 | ): Promise { 143 | await this.initializationCheck(); 144 | return new Promise(async (resolve, reject) => { 145 | const confirmationFunction = (err: any, ok: Replies.Empty) => { 146 | if (err !== null) { 147 | this.logger.error(`Publish failed: ${err.message}`); 148 | return reject(NACKED); 149 | } 150 | resolve({ status: 'ok' }); 151 | }; 152 | await this.rmqNestjsConnectService.publish( 153 | { 154 | exchange: this.options.exchange.exchange, 155 | routingKey: topic, 156 | content: this.serDes.serialize(message), 157 | options: { 158 | appId: this.options.serviceName, 159 | timestamp: new Date().getTime(), 160 | ...options, 161 | }, 162 | }, 163 | confirmationFunction, 164 | ); 165 | if (this.extendedOptions?.typeChannel !== TypeChannel.CONFIRM_CHANNEL) { 166 | resolve({ status: 'ok' }); 167 | } 168 | }); 169 | } 170 | 171 | private async listenQueue(message: ConsumeMessage | null): Promise { 172 | try { 173 | const route = this.getRouteByTopic(message.fields.routingKey); 174 | const consumer = this.getConsumer(route); 175 | const messageParse = this.deserializeMessage(message.content, consumer); 176 | const result = await this.handle(message, messageParse, consumer); 177 | if (message.properties.replyTo) { 178 | await this.sendReply( 179 | message.properties.replyTo, 180 | consumer, 181 | result, 182 | message.properties.correlationId, 183 | ); 184 | } 185 | } catch (error) { 186 | this.logger.error('Error processing message', { error, message }); 187 | this.rmqNestjsConnectService.nack(message, false, false); 188 | } 189 | } 190 | 191 | private async handle( 192 | message: ConsumeMessage, 193 | messageParse: any, 194 | consumer?: MetaTagEndpoint, 195 | ): Promise { 196 | if (!consumer) return this.buildErrorResponse(NON_ROUTE); 197 | const errorMessages = await this.validate(messageParse, consumer.validate); 198 | if (errorMessages) return this.buildErrorResponse(errorMessages); 199 | const middlewareResult = await this.executeMiddlewares(consumer, message, messageParse); 200 | const interceptorsReversed = await this.interceptorsReverse(consumer, message, messageParse); 201 | if (middlewareResult.content != null) return middlewareResult; 202 | if (consumer.handler) { 203 | const result = await this.handleMessage(consumer.handler, messageParse, message); 204 | await this.reverseInterceptors(interceptorsReversed, result.content, message); 205 | return result; 206 | } 207 | } 208 | 209 | private async reverseInterceptors( 210 | interceptorsReversed: ReverseFunction[], 211 | result: any, 212 | message: ConsumeMessage, 213 | ) { 214 | for (const revers of interceptorsReversed.reverse()) await revers(result, message); 215 | } 216 | 217 | private async executeMiddlewares( 218 | consumer: MetaTagEndpoint, 219 | message: ConsumeMessage, 220 | messageParse: any, 221 | ): Promise { 222 | const middlewares = this.getMiddlewares(consumer); 223 | const result = { content: null, headers: {} }; 224 | try { 225 | for (const middleware of middlewares) { 226 | const middlewareResult = await middleware.use(message, messageParse); 227 | if (middlewareResult != undefined) result.content = middlewareResult; 228 | } 229 | } catch (error) { 230 | result.headers = this.rmqErrorService.buildError(error); 231 | } 232 | return result; 233 | } 234 | 235 | private async interceptorsReverse( 236 | consumer: MetaTagEndpoint, 237 | message: ConsumeMessage, 238 | messageParse: any, 239 | ): Promise { 240 | const interceptors: any = this.getInterceptors(consumer.interceptors); 241 | const interceptorsReversed: ReverseFunction[] = []; 242 | for (const intercept of interceptors) { 243 | const fnReversed = await intercept(message, messageParse); 244 | interceptorsReversed.push(fnReversed); 245 | } 246 | return interceptorsReversed; 247 | } 248 | 249 | private getInterceptors(consumerInterceptors: CallbackFunctionVariadic[]) { 250 | const moduleInterceptors = this.interceptors.map(token => { 251 | const instance = this.moduleRef.get(token); 252 | return instance.intercept.bind(instance); 253 | }); 254 | return moduleInterceptors.concat(consumerInterceptors); 255 | } 256 | 257 | private getConsumer(route: string): MetaTagEndpoint { 258 | return this.rmqMessageTags.get(route) || this.rmqMessageTags.get(NON_ROUTE); 259 | } 260 | 261 | private deserializeMessage(content: Buffer, consumer?: MetaTagEndpoint) { 262 | return consumer?.serdes?.deserialize 263 | ? consumer.serdes.deserialize(content) 264 | : this.serDes.deserialize(content); 265 | } 266 | 267 | private getMiddlewares(consumer: MetaTagEndpoint): IRmqMiddleware[] { 268 | return this.middlewares.concat(consumer.middlewares).map((middleware: any) => new middleware()); 269 | } 270 | 271 | private async validate( 272 | messageParse: object, 273 | paramType: new () => object, 274 | ): Promise { 275 | if (!paramType) return; 276 | const object = Object.assign(new paramType(), messageParse); 277 | const errors = await validate(object); 278 | if (errors.length) { 279 | const errorMessages = errors.flatMap(error => Object.values(error.constraints)).join('; '); 280 | return errorMessages; 281 | } 282 | } 283 | 284 | private async handleMessage( 285 | handler: IConsumeFunction, 286 | messageParse: string, 287 | message: ConsumeMessage, 288 | ): Promise { 289 | const result = { content: {}, headers: {} }; 290 | try { 291 | result.content = (await handler(messageParse, message)) || {}; 292 | } catch (error) { 293 | result.headers = this.rmqErrorService.buildError(error); 294 | } 295 | return result; 296 | } 297 | 298 | private async sendReply( 299 | replyTo: string, 300 | consumer: MetaTagEndpoint, 301 | result: IResult, 302 | correlationId: string, 303 | ) { 304 | const serializedResult = 305 | consumer.serdes?.serialize(result.content) || this.serDes.serialize(result.content); 306 | await this.rmqNestjsConnectService.sendToReplyQueue({ 307 | replyTo, 308 | content: serializedResult, 309 | headers: result.headers, 310 | correlationId, 311 | }); 312 | } 313 | 314 | private async listenReplyQueue(message: ConsumeMessage | null): Promise { 315 | if (message.properties.correlationId) { 316 | this.sendResponseEmitter.emit(message.properties.correlationId, message); 317 | } 318 | } 319 | 320 | private async bindQueueExchange() { 321 | const { queue: queueName, consumeOptions } = this.options.queue; 322 | if (!this.rmqMessageTags?.size) return this.logger.warn(NON_DECLARED_ROUTE); 323 | const queue = await this.rmqNestjsConnectService.assertQueue( 324 | TypeQueue.QUEUE, 325 | this.options.queue, 326 | ); 327 | this.rmqMessageTags.forEach(async (_, key) => { 328 | await this.rmqNestjsConnectService.bindQueue({ 329 | queue: queue.queue, 330 | source: this.exchange.exchange, 331 | pattern: key.toString(), 332 | }); 333 | }); 334 | await this.rmqNestjsConnectService.listenQueue( 335 | queueName, 336 | this.listenQueue.bind(this), 337 | consumeOptions, 338 | ); 339 | } 340 | 341 | private async assertReplyQueue() { 342 | const { queue, options, consumeOptions } = this.options.replyTo; 343 | this.replyToQueue = await this.rmqNestjsConnectService.assertQueue(TypeQueue.REPLY_QUEUE, { 344 | queue, 345 | options, 346 | }); 347 | await this.rmqNestjsConnectService.listenReplyQueue( 348 | this.replyToQueue.queue, 349 | this.listenReplyQueue.bind(this), 350 | consumeOptions, 351 | ); 352 | } 353 | 354 | private buildErrorResponse(errorMessage: string): IResult { 355 | return { 356 | content: {}, 357 | headers: this.rmqErrorService.buildError( 358 | new RMQError(errorMessage, this.options.serviceName), 359 | ), 360 | }; 361 | } 362 | 363 | public ack(...params: Parameters): ReturnType { 364 | return this.rmqNestjsConnectService.ack(...params); 365 | } 366 | 367 | public nack(...params: Parameters): ReturnType { 368 | return this.rmqNestjsConnectService.nack(...params); 369 | } 370 | 371 | private async initializationCheck() { 372 | if (this.isInitialized) return; 373 | await new Promise(resolve => setTimeout(resolve, INITIALIZATION_STEP_DELAY)); 374 | await this.initializationCheck(); 375 | } 376 | 377 | private getRouteByTopic(topic: string): string { 378 | for (const route of this.rmqMessageTags.keys()) { 379 | const regex = toRegex(route); 380 | const isMatch = regex.test(topic); 381 | if (isMatch) return route; 382 | } 383 | return ''; 384 | } 385 | 386 | private scanMetaTags() { 387 | this.rmqMessageTags = this.metaTagsScannerService.scan(RMQ_MESSAGE_META_TAG, this.moduleToken); 388 | } 389 | 390 | async onModuleDestroy() { 391 | this.sendResponseEmitter.removeAllListeners(); 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nest.js with RabbitMQ 2 | 3 | ![logo](https://github.com/DIY0R/nestjs-rabbitmq/raw/main/img/logo.png) 4 | 5 | [![version](https://img.shields.io/npm/v/@diy0r/nestjs-rabbitmq.svg)](https://www.npmjs.com/package/@diy0r/nestjs-rabbitmq) 6 | [![npm downloads](https://img.shields.io/npm/dt/@diy0r/nestjs-rabbitmq.svg)](https://www.npmjs.com/package/@diy0r/nestjs-rabbitmq) 7 | [![jsDocs.io](https://img.shields.io/badge/jsDocs.io-reference-yellow)](https://www.jsdocs.io/package/@diy0r/nestjs-rabbitmq) 8 | [![license](https://badgen.net/npm/license/@diy0r/nestjs-rabbitmq)](https://www.npmjs.com/package/@diy0r/nestjs-rabbitmq) 9 | [![checker](https://github.com/DIY0R/nestjs-rabbitmq/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/DIY0R/nestjs-rabbitmq/actions/workflows/main.yml) 10 | 11 | --- 12 | 13 | # Description 14 | 15 | This library simplifies interaction with RabbitMQ using a modular approach, where each module is linked to a specific exchange and queue. It is ideal for scalable applications, such as API Gateways. The library offers flexible initialization methods (`ForRoot`, `ForRootAsync`), supports RPC (`send`), notifications (`notify`), direct queue messaging, and provides middleware, validation, and interceptors for message processing. Additional features include serialization/deserialization, health check tools, and advanced error handling (`RMQErrorHandler`). It also includes extensive configuration options (`ForFeature`, `ForFeatureAsync`) and support for advanced RabbitMQ pattern topics. 16 | 17 | --- 18 | 19 | # Contents 20 | 21 | - [Installation](#installation) 22 | - [Module Initialization](#module-initialization) 23 | - [ForRoot](#forroot) 24 | - [ForRootAsync - Async initialization](#forrootasync-async-initialization) 25 | - [Connect via URI](#connect-via-uri) 26 | - [RmqGlobalService](#rmqglobalservice) 27 | - [notify](#method-notify-if-you-want-to-just-notify-services) 28 | - [send (RPC)](#method-send) 29 | - [Send to Queue](#method-sendtoqueue) 30 | - [Access to the channel](#channel) 31 | - [Serialization/Deserialization](#serializationdeserialization) 32 | - [Module-Specific Configuration](#module-specific-configuration) 33 | - [ForFeature](#forfeature) 34 | - [ForFeatureAsync - Async initialization](#forfeatureasync---async-initialization) 35 | - [Receiving messages](#receiving-messages) 36 | - [Validation](#validation) 37 | - [Middleware](#middleware) 38 | - [Interceptor](#interceptor) 39 | - [RmqService](#rmqservice) 40 | - [send(RPC) and notify - methods](#send-and-notify---methods) 41 | - [healthCheck, ack and nack - methods](#healthcheckack-and-nack---methods) 42 | - [Serialization/Deserialization in specific module](#serializationdeserialization-in-specific-module) 43 | - [Throw error and handling](#throw-error-and-handling) 44 | - [RMQErrorHandler](#rmqerrorhandler) 45 | - [Catch Error](#catch-error) 46 | - [Throw error](#throw-error) 47 | 48 | ## Installation 49 | 50 | Start by installing the `@diy0r/nestjs-rabbitmq` package: 51 | 52 | ```bash 53 | npm i @diy0r/nestjs-rabbitmq 54 | ``` 55 | 56 | ## Module Initialization 57 | 58 | In your root module, import `RmqModule`: 59 | 60 | ### ForRoot 61 | 62 | ```typescript 63 | import { RmqModule, IRMQExtendedOptions } from '@diy0r/nestjs-rabbitmq'; 64 | 65 | const extendedOptions: IRMQExtendedOptions = { 66 | typeChannel: TypeChannel.CONFIR_CHANNEL, 67 | globalBroker: { 68 | replyTo: { 69 | queue: '', 70 | options: { exclusive: true }, 71 | consumeOptions: { noAck: true }, 72 | }, 73 | //.. 74 | }, 75 | //... 76 | }; 77 | 78 | @Module({ 79 | imports: [ 80 | RmqModule.forRoot({ 81 | connectOptions: { 82 | username: 'username', 83 | password: 'password', 84 | hostname: 'localhost', 85 | port: 5672, 86 | vhost: '/', 87 | protocol: 'amqp', 88 | }, 89 | extendedOptions, //optional 90 | }), 91 | ], 92 | providers: [SomeService], 93 | }) 94 | export class AppModule {} 95 | ``` 96 | 97 | ### ForRootAsync (Async initialization) 98 | 99 | ```typescript 100 | @Module({ 101 | imports: [ 102 | RmqModule.forRootAsync({ 103 | useFactory: async (...providers: any[]) => ({ 104 | connectOptions: { 105 | username: 'username', 106 | password: 'password', 107 | hostname: 'localhost', 108 | port: 5672, 109 | vhost: '/', 110 | protocol: 'amqp', 111 | }, 112 | extendedOptions, //optional 113 | }), 114 | inject: [], 115 | imports: [], 116 | }), 117 | ], 118 | providers: [SomeService], 119 | }) 120 | export class AppModule {} 121 | ``` 122 | 123 | ### Connect via URI 124 | 125 | You can also connect using the standard URI. For more information, see the [RabbitMQ URI Specification](https://www.rabbitmq.com/docs/uri-spec). 126 | 127 | ```typescript 128 | RmqModule.forRoot({ 129 | connectOptions: 'amqp://username:password@localhost:5672/', 130 | extendedOptions, //optional 131 | }); 132 | ``` 133 | 134 |
135 | 136 | In `forRoot({ connectOptions, extendedOptions })`, we pass an object consisting of two parameters. The first, connectOptions, is used for connections, and the second, extendedOptions, is for additional settings. 137 | 138 | - **connectOptions: IRMQConnectConfig** - standard parameters [amqlib.connect](https://amqp-node.github.io/amqplib/channel_api.html#connect) for connection. Parameters can be an object or a URI. 139 | - **extendedOptions: IRMQExtendedOptions** - environment settings (optional) 140 | 141 | - **typeChannel** - [channel type](https://amqp-node.github.io/amqplib/channel_api.html#channels), default is `TypeChannel.CHANNEL` 142 | - **prefetch** - Configuration for message prefetching. [See](https://amqp-node.github.io/amqplib/channel_api.html#channel_prefetch) 143 | - **count** - The maximum number of messages that can be sent over the channel before receiving an acknowledgment. A falsy value indicates no limit. 144 | - **isGlobal** - If `true`, applies the prefetch limit to the entire channel (for versions before RabbitMQ 3.3.0). For newer versions, this parameter is ignored as prefetch is applied per consumer. 145 | - **socketOptions** - here you can configure SSL/TLS. See [SSL/TLS](https://amqp-node.github.io/amqplib/ssl.html) and [Client properties](https://amqp-node.github.io/amqplib/channel_api.html#client-properties) 146 | - **globalBroker** - specify if you want to use RPC from `RmqGlobalService` 147 | - **replyTo** - needed for setting up the `replyTo` queue 148 | - **queue** - queue name. It's recommended to leave it as an empty string so RabbitMQ will automatically generate a unique name. 149 | - **options** - options passed as the second parameter in [assertQueue](https://amqp-node.github.io/amqplib/channel_api.html#channel_assertQueue) 150 | - **consumeOptions** - configure the consumer with a callback that will be invoked with each message. [See](https://amqp-node.github.io/amqplib/channel_api.html#channel_consume) 151 | - **errorHandler** (class) - custom error handler for dealing with errors from replies that extends [`RMQErrorHandler`](#throw-error-and-handling). 152 | - **serDes** (object) - The serDes parameter is an object that defines serialization and deserialization functions for handling messages. [See](#serializationdeserialization) 153 | - **messageTimeout** - Number of milliseconds the `send` method will wait for the response before a timeout error. Default is 40,000. 154 | - **serviceName** - an arbitrary identifier for the originating application 155 | 156 | We recommend specifying the `TypeChannel.CONFIR_CHANNEL` channel type to get more accurate statuses for your requests. 157 | 158 | ## RmqGlobalService 159 | 160 | After successful connection, `RmqGlobalService` will be available globally and you will be able to import your module from any part of your application and send messages. 161 | 162 | ```typescript 163 | @Injectable() 164 | export class SomeService { 165 | constructor(private readonly rmqGlobalService: RmqGlobalService) {} 166 | 167 | //... 168 | } 169 | ``` 170 | 171 | We recommend specifying interfaces for the objects you send and receive. 172 | 173 | ```ts 174 | interface IReply { 175 | message: object; 176 | } 177 | 178 | interface ISend { 179 | age: number; 180 | name: string; 181 | } 182 | ``` 183 | 184 | ### Method `notify` If you want to just notify services 185 | 186 | ```ts 187 | async sendNotify(obj: ISend) { 188 | const { status } = await this.rmqGlobalService.notify( 189 | 'exchange', 190 | 'user.login', 191 | obj, 192 | ); 193 | return status; 194 | } 195 | ``` 196 | 197 | - **'this.rmqGlobalService.notify(exchange, routingKey, options)'** - asynchronous request that returns the message status 198 | - **exchange** - exchange name 199 | - **routingKey** - routing key 200 | - **options** - standard [publish options](https://amqp-node.github.io/amqplib/channel_api.html#channelpublish) (optional) 201 | 202 | ### Method `send` 203 | 204 | If you have defined `globalBroker` in `forRoot`, you will have access to the RPC method `send`,otherwise, you will catch an error when calling it. 205 | 206 | ```ts 207 | async sendMessage(obj: ISend) { 208 | const message = await this.rmqGlobalService.send( 209 | 'exchange name', 210 | 'user.rpc', 211 | obj, 212 | ); 213 | return message; 214 | } 215 | ``` 216 | 217 | - **'this.rmqGlobalService.send(exchange, routingKey, options)'** - asynchronous request. 218 | 219 | - **exchange** - exchange name 220 | - **routingKey** - routing key 221 | - **options** - standard [publish options](https://amqp-node.github.io/amqplib/channel_api.html#channelpublish) (optional) 222 | - **timeout** - if supplied, the message will have its own timeout.(custom parameter) 223 | 224 | ### Method `sendToQueue` 225 | 226 | Unlike the standard [sendToQueue](https://amqp-node.github.io/amqplib/channel_api.html#channel_sendToQueue) method from amqlib, this asynchronous method differs in that messages go through [Serialization](#serializationdeserialization) before being sent. 227 | 228 | ```ts 229 | async sendToQueue(queue: string, obj:ISend) { 230 | const status = await this.rmqGlobalService.sendToQueue(queue, obj); 231 | return status; 232 | } 233 | ``` 234 | 235 | Return either true, meaning “keep sending”, or false, meaning “please wait for a ‘drain’ event”.[See Flow control 236 | ](https://amqp-node.github.io/amqplib/channel_api.html#flowcontrol) 237 | 238 | - **'this.rmqGlobalService.sendToQueue()'** - asynchronous function 239 | - queue - queue name 240 | 241 | ### Channel 242 | 243 | If you want access to the standard RabbitMQ channel, you always have access to async getter `this.rmqGlobalService.channel` and all its methods [See](https://amqp-node.github.io/amqplib/channel_api.html#channel). 244 | 245 | ```ts 246 | async getChannel() { 247 | const channel: Channel | ConfirmChannel = await this.rmqGlobalService.channel; 248 | return channel; 249 | } 250 | ``` 251 | 252 | ### Serialization/Deserialization 253 | 254 | By default, messages are parsed using the JSON.parse method when they are received and converted to a string using JSON.stringify when they are published. If you want to change this behavior you can use your own parsers. 255 | 256 | ```ts 257 | const extendedOptions: IRMQExtendedOptions = { 258 | typeChannel: TypeChannel.CONFIR_CHANNEL, 259 | globalBroker: { 260 | serDes: { 261 | deserialize: (message: Buffer): any => OtherDeserializer(message), 262 | serialize: (message: any): Buffer => OtherSerialize(message), 263 | }, 264 | //... 265 | }, 266 | //... 267 | }; 268 | ``` 269 | 270 | #### Method `deserialize` 271 | 272 | The `deserialize` method is responsible for converting a message from its raw format (typically a Buffer in Node.js) into a JavaScript object or another suitable format for processing. 273 | 274 | #### Method `serialize` 275 | 276 | The `serialize` method is responsible for converting a JavaScript object or any other data format into a raw format (typically a Buffer) suitable for transmission over RabbitMQ. 277 | 278 | In this case, it will be applied to all methods of [`RmqGlobalService`](#RmqGlobalService). 279 | 280 | ## Module-Specific Configuration 281 | 282 | The `forFeature` and `forFeatureAsync` methods allow configuring RabbitMQ parameters at the specific module level in your NestJS application. This is useful for setting up different exchanges, queues, and other RabbitMQ parameters specific to that module. 283 | 284 | ### forFeature 285 | 286 | To use forFeature in your module, import `RmqModule` and configure the parameters in the `forFeature` method. 287 | 288 | ```typescript 289 | @Module({ 290 | imports: [ 291 | RmqModule.forFeature({ 292 | exchange: { 293 | exchange: 'exchange_name', 294 | type: 'topic', 295 | options: { 296 | durable: true, 297 | autoDelete: true, 298 | }, 299 | }, 300 | queue: { 301 | queue: 'queue', 302 | options: { durable: true }, 303 | consumeOptions: { noAck: false }, 304 | }, 305 | replyTo: { 306 | queue: '', 307 | options: { exclusive: true }, 308 | consumeOptions: { noAck: true }, 309 | }, 310 | }), 311 | ], 312 | providers: [MyService, MyRmqEvents], 313 | }) 314 | export class MyModule {} 315 | ``` 316 | 317 | ### forFeatureAsync - Async initialization 318 | 319 | ```ts 320 | RmqModule.forFeatureAsync({ 321 | inject: [], 322 | imports: [], 323 | useFactory: async () => ({ 324 | exchange, 325 | queue, 326 | replyTo, 327 | //... 328 | }), 329 | 330 | interceptors: [], 331 | //.. 332 | }); 333 | ``` 334 | 335 | useFactory should return an object of type `IModuleBroker`, and `serdes`, `interceptors`, and `middlewares` come after `useFactory`, not inside it, because these parameters are needed to extend the functionality. That's why they come after it. 336 | 337 |
338 | In the `forFeature(ModuleBroker)` method, you need to pass an object of type `IModuleBroker`: 339 | 340 | - **exchange** - exchange parameters 341 | - **exchange** - The name of the exchange, which should be unique. 342 | - **type** - exchange type (Currently only the topic is supported) 343 | - **options** - The default queue parameters from `Options.AssertExchange`.(optional) See [Channel.assertExchange](https://amqp-node.github.io/amqplib/channel_api.html#channel_assertExchange). 344 | - **queue** - An object containing the queue parameters. (optional) 345 | - **queue** - The name of the queue, which should be unique. 346 | - **options** - The default queue parameters from `Options.AssertQueue`. See [Channel AssertQueue](https://amqp-node.github.io/amqplib/channel_api.html#channel_assertQueue). 347 | - **consumeOptions** - configure the consumer with a callback that will be invoked with each message. See [Channel Consume](https://amqp-node.github.io/amqplib/channel_api.html#channel_consume) 348 | - **replyTo** - needed for setting up the `replyTo` queue (optional) 349 | - **queue** - The name of the queue, which should be unique. 350 | - **options** - The default queue parameters from `Options.AssertQueue`. See [Channel AssertQueue](https://amqp-node.github.io/amqplib/channel_api.html#channel_assertQueue). 351 | - **consumeOptions** - configure the consumer with a callback that will be invoked with each message. See [Channel Consume](https://amqp-node.github.io/amqplib/channel_api.html#channel_consume) 352 | - **errorHandler** (class) - custom error handler for dealing with errors from replies that extends [`RMQErrorHandler`](#throw-error-and-handling). 353 | - **serviceName** - The name of your service. (optional) 354 | - **messageTimeout** - The timeout for waiting for a response, with a default value of 40000ms. (optional) 355 | 356 | ### Receiving messages 357 | 358 | In `@diy0r/nestjs-rabbitmq`, requests are processed through the `@MessageRoute` and `@MessageNonRoute` decorators. You need to declare the `queue` to use these decorators. 359 | 360 | ```typescript 361 | @Injectable() 362 | export class MyRmqEvents { 363 | constructor(private readonly rmqService: RmqService) {} 364 | @MessageRoute('text.text') 365 | received(obj: any, consumeMessage: ConsumeMessage) { 366 | this.rmqService.ack(consumeMessage); // call if 'consumeOptions: { noAck: false }' 367 | return { message: obj }; 368 | } 369 | @MessageRoute('*.*.rpc') // subscribe with a pattern! 370 | receivedTopic(obj: any, consumeMessage: ConsumeMessage) { 371 | return { message: obj }; 372 | } 373 | @MessageRoute('rpc.#') // subscribe with a pattern! 374 | receivedTopicPattern(obj: any, consumeMessage: ConsumeMessage) { 375 | //... 376 | return { message: obj }; 377 | } 378 | @MessageRoute('*.rpc.mix.#') //subscribe with a mix pattern! 379 | recivedMixTopic(obj: any, consumeMessage: ConsumeMessage) { 380 | return { message: obj }; 381 | } 382 | @MessageRoute('notify.rpc') 383 | receivedTopicNotify(obj: any, consumeMessage: ConsumeMessage) { 384 | this.rmqService.ack(consumeMessage); 385 | //... 386 | } 387 | @MessageNonRoute() 388 | receivedNonRoute(obj: any, consumeMessage: ConsumeMessage) { 389 | this.rmqService.ack(consumeMessage); 390 | return { message: obj }; 391 | } 392 | } 393 | ``` 394 | 395 | ```ts 396 | @MessageRoute(RoutingKey: string) 397 | method(message: ISend, consumeMessage: ConsumeMessage) {} 398 | ``` 399 | 400 | - `message` - what the listener receives 401 | - `consumeMessage` - more information from the message (not just content) from amqlib 402 | 403 | The @MessageRoute decorator will automatically bind the queue and RoutingKey to the exchange specified in the forFeature method, and this routing key will only be visible inside the specific module (e.g., [`MyModule`](#import-and-usage)). Just like magic!
404 | In some cases, when a non-processable message arrives in the queue, it will stay there forever unless it is processed. In such cases, the `@MessageNonRoute` decorator can help. The method bound to this decorator will be called only when the request cannot find a bound routing key to the exchange. 405 | 406 | If you want to manually acknowledge messages, set `consumeOptions: { noAck: false }` in the queue. If you want the library to automatically acknowledge messages, set `noAck: true`, so you don't have to explicitly call `ack`. 407 | 408 | Here’s a more polished version of your text: 409 | 410 | --- 411 | 412 | ### Validation 413 | 414 | `@diy0r/nestjs-rabbitmq` integrates seamlessly with `class-validator` to validate incoming messages. To use it, annotate your route method with `@RMQValidate()`. 415 | 416 | ```typescript 417 | @MessageRoute('message.valid') 418 | @RMQValidate() 419 | getValidMessage(obj: MyClass, consumeMessage: ConsumeMessage) { 420 | this.rmqService.ack(consumeMessage); 421 | return { message: obj }; 422 | } 423 | ``` 424 | 425 | Here, `MyClass` represents the validation schema: 426 | 427 | ```typescript 428 | import { IsInt, IsString, MaxLength } from 'class-validator'; 429 | 430 | export class MyClass { 431 | @IsString() 432 | @MaxLength(5, { message: 'The name must be less than 5 characters.' }) 433 | name: string; 434 | 435 | @IsInt() 436 | age: number; 437 | } 438 | ``` 439 | 440 | If the input message fails validation, the library will immediately send back an error without invoking your method, middlewares and interceptors. 441 | 442 | ### Middleware 443 | 444 | Middleware allows you to execute additional logic before message processing.You can declare middleware at various levels, including the module level, provider level, and specific endpoint level. 445 | It's important to note that the middleware runs before any interceptors. 446 | 447 | To declare an middleware, implement the `IRmqMiddleware` abstract class. 448 | 449 | ```ts 450 | export class EventMiddleware implements IRmqMiddleware { 451 | async use(message: ConsumeMessage, content: any): Promise { 452 | content.args = '1,2,...'; 453 | } 454 | } 455 | ``` 456 | 457 | or you can directly return: 458 | 459 | ```ts 460 | export class EventMiddleware implements IRmqMiddleware { 461 | async use(message: ConsumeMessage, content: any): Promise { 462 | if (content.args !== '2,1') return { status: 'error' }; 463 | } 464 | } 465 | ``` 466 | 467 | If nothing is returned, the execution of the middleware chain will continue. 468 | 469 | EventMiddleware implements the `use` method, which takes two parameters: `message`, which is the standard `ConsumeMessage` from amqplib, and `content`, which is your message that has passed through [deserialization](#serializationdeserialization-in-specific-module). 470 | 471 | #### Module Level 472 | 473 | At the module level, you can declare middleware that will apply to all message handlers within that module. 474 | 475 | ```ts 476 | @Module({ 477 | imports: [ 478 | RmqModule.forFeature({ 479 | exchange: { 480 | /* exchange parameters */ 481 | }, 482 | queue: { 483 | /* queue parameters */ 484 | }, 485 | middlewares: [EventMiddleware, OtherMiddleware], // Middleware is applied to all handlers in the module 486 | }), 487 | ], 488 | providers: [MyService, MyRmqEvents], 489 | }) 490 | export class MyModule {} 491 | ``` 492 | 493 | #### Provider Level 494 | 495 | At the provider level, you can declare middleware that will apply to all message handlers within that provider. 496 | 497 | ```ts 498 | @Injectable() 499 | @RmqMiddleware(EventMiddleware) // Middleware is applied to all methods in the MyRmqEvents 500 | export class MyRmqEvents { 501 | @MessageRoute('text.text') 502 | received(obj: any, consumeMessage: ConsumeMessage) { 503 | return { message: obj }; 504 | } 505 | 506 | // Class methods ... 507 | } 508 | ``` 509 | 510 | #### Specific Endpoint Level 511 | 512 | Middleware can be applied at the specific endpoint (method) level. 513 | 514 | ```ts 515 | @Injectable() 516 | export class MyRmqEvents { 517 | @MessageRoute('text.text') 518 | @RmqMiddleware(EventMiddleware) // Middleware is applied only to this method 519 | handleTextText(obj: any, consumeMessage: ConsumeMessage) { 520 | return { message: obj }; 521 | } 522 | } 523 | ``` 524 | 525 |
526 | 527 | Initially, middleware at the module level will be invoked, then at the class level, followed by the endpoint level. 528 | 529 | ### Interceptor 530 | 531 | Interceptors allow you to intercept and modify the processing of a message both before and after it is processed by a handler, just like [Interceptors from Nest.js](#https://docs.nestjs.com/interceptors). You can declare interceptors at various levels, including the module level, the provider level, and the specific endpoint level. 532 | It's important to note that interceptors are executed after the middleware! 533 | 534 | To declare an interceptor, implement the abstract class `IRmqInterceptor` with a decorator `@Injectable`. 535 | 536 | ```ts 537 | @Injectable() 538 | export class EventInterceptor implements IRmqInterceptor { 539 | constructor(private readonly rmqSerivce: RmqService) {} 540 | async intercept(message: ConsumeMessage, content: any): Promise { 541 | console.log('Before...'); 542 | return async (content: any, message: ConsumeMessage) => { 543 | console.log(`After... ${Date.now() - now}ms`); 544 | }; 545 | } 546 | } 547 | ``` 548 | 549 | EventInterceptor implements the `intercept` method, which takes two parameters: `message`, which is the standard `ConsumeMessage` from amqplib, and `content`, which is your message that has passed through [deserialization](#serializationdeserialization-in-specific-module). The `intercept` method returns an asynchronous function that will be invoked after processing. 550 | 551 | #### Module Level 552 | 553 | At the module level, you can declare interceptors that will apply to all message handlers within that module. 554 | 555 | ```ts 556 | @Module({ 557 | imports: [ 558 | RmqModule.forFeature({ 559 | exchange: { 560 | /* exchange parameters */ 561 | }, 562 | queue: { 563 | /* queue parameters */ 564 | }, 565 | interceptors: [EventInterceptor, OtherInterceptor], // Interceptors are applied to all handlers in the module! 566 | }), 567 | ], 568 | providers: [MyService, MyRmqEvents], 569 | }) 570 | export class MyModule {} 571 | ``` 572 | 573 | The `interceptor` will be invoked from left to right. 574 | 575 | #### Provider Level 576 | 577 | At the provider level, you can declare interceptors that will apply to all message handlers within that provider. 578 | 579 | ```ts 580 | @Injectable() 581 | @RmqInterceptor(EventInterceptor) // Interceptor is applied to all methods in the MyRmqEvents 582 | export class MyRmqEvents { 583 | @MessageRoute('text.text') 584 | received(obj: any, consumeMessage: ConsumeMessage) { 585 | return { message: obj }; 586 | } 587 | 588 | // Class methods ... 589 | } 590 | ``` 591 | 592 | #### Specific Endpoint Level 593 | 594 | At the specific endpoint (method) level, allowing you to configure interceptor for each endpoint individually. 595 | 596 | ```ts 597 | @Injectable() 598 | export class MyRmqEvents { 599 | @MessageRoute('text.text') 600 | @RmqInterceptor(EventInterceptor) // Interceptor is applied only to this method 601 | handleTextText(obj: any, consumeMessage: ConsumeMessage) { 602 | return { message: obj }; 603 | } 604 | } 605 | ``` 606 | 607 | **Initially, interceptors at the module level will be invoked, then at the class level, followed by the endpoint level, and then in reverse order.** 608 | 609 | ### RmqService 610 | 611 | If you just want to send messages and receive responses, simply specify the exchange and replyTo, and you will have access to all methods from RmqService as shown below. 612 | It is mainly useful when you are writing an API gateway and want to maintain modularity. 613 | 614 | #### send and notify - methods 615 | 616 | ```typescript 617 | @Injectable() 618 | export class MyService { 619 | constructor(private readonly rmqService: RmqService) {} 620 | 621 | async sendMessage(obj: ISend) { 622 | const message = await this.rmqGlobalService.send( 623 | 'user.rpc', 624 | obj, 625 | ); 626 | return message; 627 | } 628 | 629 | async sendNotifyService(obj: Record) { 630 | const message = await this.rmqService.notify('notify.rpc', obj); 631 | return message; 632 | } 633 | ``` 634 | 635 | - **'this.rmqService.send(routingKey, options)'** - asynchronous request 636 | 637 | - **routingKey** - routing key 638 | - **options** - standard [publish options](https://amqp-node.github.io/amqplib/channel_api.html#channelpublish) (optional) - **timeout** - if supplied, the message will have its own timeout.(custom parameter) 639 | 640 | To use `send`, you must have `replyTo` declared in forFeature. 641 | 642 | - **'this.rmqService.notify(routingKey, options)'** - asynchronous request that returns the message status 643 | - **routingKey** - routing key 644 | - **options** - standard [publish options](https://amqp-node.github.io/amqplib/channel_api.html#channelpublish) (optional) 645 | 646 | Note that in these examples we do not specify the exchange in the requests because the send and notify methods use the exchange specified on [MyModule](#import-and-usage) in method `forFeature`. 647 | 648 | #### healthCheck,ack and nack - methods 649 | 650 | ```ts 651 | const isConnected = this.rmqService.healthCheck(); 652 | ``` 653 | 654 | **'this.rmqService.healthCheck()'** - Check if you are still connected to RMQ. 655 | 656 | ```ts 657 | this.rmqService.ack(message); 658 | ``` 659 | 660 | - **'this.rmqService.ack(message:ConsumeMessage)'** - Acknowledge the given message, or all messages up to and including the given message. [See](https://amqp-node.github.io/amqplib/channel_api.html#channelack) 661 | 662 | ```ts 663 | this.rmqService.nack(message); 664 | ``` 665 | 666 | - **'this.rmqService.nack(message:ConsumeMessage)'** - Reject a message. This instructs the server to either requeue the message or throw it away (which may result in it being dead-lettered). [See](https://amqp-node.github.io/amqplib/channel_api.html#channelnack) 667 | 668 | ### Serialization/Deserialization in specific module 669 | 670 | The `serDes` allows for both the deserialization and serialization of messages. 671 | 672 | It's important to note the execution order, as this is performed only once. 673 | When a message reaches an endpoint, the framework checks for the `@SerDes` decorator on method(endpoint). If found, the methods specified there are used. If not, it searches for the decorator in the parent class(Provider). If the decorator is still not found, it falls back to using the `serDes` object from your module. If none of these are defined, the default serialization and deserialization are used. 674 | 675 | #### Module Level 676 | 677 | At the module level, the SerDes configuration applies to all message handlers within that module. This is useful for ensuring consistent message processing across the entire module. 678 | 679 | ```ts 680 | @Module({ 681 | imports: [ 682 | RmqModule.forFeature({ 683 | exchange: { 684 | /* exchange parameters */ 685 | }, 686 | queue: { 687 | /* queue parameters */ 688 | }, 689 | serDes: { 690 | deserialize: (message: Buffer): any => OtherDeserializer(message), 691 | serialize: (message: any): Buffer => OtherSerializer(message), 692 | }, // serDes is applied to all handlers in the module! 693 | }), 694 | ], 695 | }) 696 | export class MyModule {} 697 | ``` 698 | 699 | #### Provider Level 700 | 701 | You can also configure SerDes at the provider level, applying it to all message handlers within a specific class(provider). 702 | 703 | ```ts 704 | @Injectable() 705 | @SerDes({ 706 | deserialize: (message: Buffer): any => OtherDeserializer(message), 707 | serialize: (message: any): Buffer => OtherSerializer(message), 708 | }) // SerDes is applied to all methods in the MyRmqEvents 709 | export class MyRmqEvents { 710 | @MessageRoute('text.text') 711 | received(obj: any, consumeMessage: ConsumeMessage) { 712 | return { message: obj }; 713 | } 714 | 715 | // Class methods ... 716 | } 717 | ``` 718 | 719 | #### Specific Endpoint Level 720 | 721 | At the specific endpoint (method) level, allowing you to configure SerDes for each endpoint individually. 722 | 723 | ```ts 724 | @Injectable() 725 | export class MyRmqEvents { 726 | @MessageRoute('text.text') 727 | @SerDes({ 728 | deserialize: (message: Buffer): any => OtherDeserializer(message), 729 | serialize: (message: any): Buffer => OtherSerializer(message), 730 | }) // SerDes is applied only to this method 731 | handleTextText(obj: any, consumeMessage: ConsumeMessage) { 732 | return { message: obj }; 733 | } 734 | } 735 | ``` 736 | 737 | ## Throw error and handling 738 | 739 | If you want to use a custom error handler to handle errors in responses, use the errorHandler property in the `replyTo` parameters in [`forRoot`](#module-initialization) and [`forFeature`](#module-specific-configuration-forfeature), and pass a class that extends `RMQErrorHandler` 740 | 741 | ### RMQErrorHandler 742 | 743 | If you call `rmqGlobalService.send`, the `errorHandler` from `forRoot` will be used, and if you call `rmqService.send`, the `errorHandler` from `forFeature` will be used. 744 | 745 | ```ts 746 | export class MyRMQErrorHandler extends RMQErrorHandler { 747 | public static handle(headers: IRmqErrorHeaders | MessagePropertyHeaders): Error | RMQError { 748 | // do something ... 749 | return new RMQError( 750 | headers['-x-error'], 751 | headers['-x-service'], 752 | headers['-x-status-code'], 753 | headers['-x-host'], 754 | headers['-x-date'], 755 | ); 756 | } 757 | } 758 | ``` 759 | 760 | ### Catch Error 761 | 762 | ```ts 763 | async sendMessage (obj: ISend){ 764 | try { 765 | const message = await this.rmqGlobalService.send( 766 | 'user.rpc', 767 | obj, 768 | ); 769 | } catch (error: RMQError) { 770 | //... 771 | } 772 | }; 773 | ``` 774 | 775 | ### Throw error 776 | 777 | ```ts 778 | @MessageRoute() 779 | handleTextText(obj: ISend, consumeMessage: ConsumeMessage) { 780 | throw new RMQError('Error message', 2); 781 | //or 782 | throw new Error('Error message'); 783 | } 784 | ``` 785 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### Changelog 2 | 3 | All notable changes to this project will be documented in this file. Dates are displayed in UTC. 4 | 5 | #### [0.28.0](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.27.7...0.28.0) 6 | 7 | - feat: validate incoming message [`a1a833e`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/a1a833e4a56d073eaf2ee4b773d2c50217c7ab4a) 8 | - test: validation message [`c1d50c7`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/c1d50c7cb611948d9fcfe3d962e7008342b81df8) 9 | - doc: add Validation section [`65740d7`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/65740d73dba91a48d2aaffb678ab336eaeb12142) 10 | - refact: `toRegex` function to handle patterns with `*` and `#` placeholders [`bc7c288`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/bc7c28817e9eab3ac15e184b8b6e814ac3891aeb) 11 | - doc: add description [`85ca159`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/85ca1595e86935a63bcebfc4a7b0b8cb2133ba7b) 12 | - doc: correct description [`8f2fe51`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/8f2fe513ee6ec07b877f6761f1d3f9f402c2e1ab) 13 | 14 | #### [0.27.7](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.27.6...0.27.7) 15 | 16 | > 23 November 2024 17 | 18 | - Merge pull request #233 from DIY0R/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-8.15.0 [`447e8c3`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/447e8c3b03a6d1d67a89ba032b5782b332830489) 19 | - refact: bound handler if event esists [`6b1b073`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/6b1b073bb9c048430c96c5c10e3dc20c80c135dd) 20 | - Merge pull request #131 from DIY0R/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-8.3.0 [`f76984f`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/f76984f271a83eec8f4cf6a3a57e4826b5e761c5) 21 | 22 | #### [0.27.6](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.27.5...0.27.6) 23 | 24 | > 22 August 2024 25 | 26 | - refact: correct typo [`b7a8127`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/b7a81277b033bc75e5f798956527f9ba75143f57) 27 | - doc: add npm downloads bage [`c1590c8`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/c1590c8ca18471e0cbe28dffe7c09861bb37b9b0) 28 | - Merge pull request #121 from DIY0R/dependabot/npm_and_yarn/typescript-eslint/types-8.1.0 [`1a759ee`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/1a759ee027f4d6331968fb71cf7624d8e6c69f8e) 29 | 30 | #### [0.27.5](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.27.3...0.27.5) 31 | 32 | > 13 August 2024 33 | 34 | - refact: correct the typo [`1d4011a`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/1d4011a491f9c4c8df3c3a2c11c97db757c651fd) 35 | 36 | #### [0.27.3](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.26.5...0.27.3) 37 | 38 | > 5 August 2024 39 | 40 | - feat: declare prefetch on baseChanel [`896e489`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/896e489fd6472d5ab30afec1d313179296cb7cf7) 41 | - doc: prefetch on extendedOptions [`c9fdfef`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/c9fdfefdd37b13bfb968fd3838b7aa659a275000) 42 | - doc: badge jsdoc added [`7ae2eac`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/7ae2eac6f92af2dccca50fb9563c44dd0a1087a7) 43 | 44 | #### [0.26.5](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.25.18...0.26.5) 45 | 46 | > 3 August 2024 47 | 48 | - feat: forFeatureAsync method [`5c5e96f`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/5c5e96fa414fa9336eaf8f120502432711edc055) 49 | - refact: rename variables and add extendedProviders [`b100d01`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/b100d012d1b0b1b056146cc1ab90f745b271327d) 50 | - test: forFeatureAsync module [`eab1269`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/eab1269b13f6d00faf4db801369ef1fe681828b0) 51 | - doc: forFeatureAsync - async initialization section added [`80e815e`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/80e815e9067c4f3162bce3ec974246cadd3bb370) 52 | - fix: imports support [`1f68618`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/1f68618e9f7b2e6f7d01e9a026408f907da80a29) 53 | 54 | #### [0.25.18](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.25.6...0.25.18) 55 | 56 | > 1 August 2024 57 | 58 | - refact: combine parameters into one in forRoot [`e34559e`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/e34559e4edd048c8dd69161b5f9647e8885d40c4) 59 | - doc: combine parameters into one in forRoot [`f18de79`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/f18de79ac5e0d78cf53a7d8f932330ca00f67649) 60 | - test: use forRootAsync [`e6416ad`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/e6416adfc3c41b71072e851820fab362a1c5ab58) 61 | - refact: change name param [`52c8898`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/52c88982b3d00fcde95caa01772e6cacd5d6ee87) 62 | - fix: default array [`4649295`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/4649295d312eb91e7b6500e25a38d609aff97a40) 63 | 64 | #### [0.25.6](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.24.1...0.25.6) 65 | 66 | > 30 July 2024 67 | 68 | - feat: custom handle static method [`d733d05`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/d733d05e6560228bb3ad82e03f27620a396bb5cc) 69 | - doc: throw error and handling [`3d25279`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/3d2527983f047d660e08f5e25207e9a244a5c5c4) 70 | - test: custom error handler [`9d2d8bc`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/9d2d8bcffc2589b32cd7dd20c25c20ce25900006) 71 | - refact: asynchronous method sendToQueue [`e844bd1`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/e844bd1a5fa87c5befb64dc981b1a8aeba798d1a) 72 | - doc: changed the method sendToQueue to asynchronous [`d2e8f15`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/d2e8f15236f10a03546055c110739742d297584d) 73 | - fix: handling non-existent routes [`b5b3165`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/b5b316544596e7a3d22c868eccc741137673a94f) 74 | 75 | #### [0.24.1](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.23.3...0.24.1) 76 | 77 | > 28 July 2024 78 | 79 | - feat: throw custom error [`907c1ea`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/907c1ea4a1528daea04b626b96622244010a6406) 80 | - test: received error [`d563962`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/d563962b2c43d43ca7cbc2470b2d8ff55f38d7b3) 81 | - doc: link to serdes [`71572f9`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/71572f9683eb8ee77d7a0004895c47c3512a28a8) 82 | 83 | #### [0.23.3](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.22.48...0.23.3) 84 | 85 | > 25 July 2024 86 | 87 | - chore: bump @typescript-eslint/eslint-plugin from 7.16.0 to 7.17.0 [`#70`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/pull/70) 88 | - chore: Revert bump @types/eslint from 8.56.10 to 9.6.0 [`#75`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/pull/75) 89 | - feat: injectable interceptor [`f3daa0a`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/f3daa0aca182dd5e0ee6d7c36154fe1fdca26358) 90 | - test: injectable interceptor [`621123b`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/621123b55afc79ef9bdc023d2532c8be730f8377) 91 | - doc: rewrote interceptor to injectable interceptor [`6658188`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/6658188a92a08328096c4bdb60ea816369c50241) 92 | - Revert "chore: bump @types/eslint from 8.56.10 to 9.6.0" [`d5ffdb4`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/d5ffdb4171ec1ce6dd33ebc4405af02f955cfcf1) 93 | - Merge pull request #72 from DIY0R/dependabot/npm_and_yarn/types/eslint-9.6.0 [`c1cbc7f`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/c1cbc7f146d3f074f323ec88618aee42b0581e2e) 94 | - Merge pull request #71 from DIY0R/dependabot/npm_and_yarn/typescript-eslint/parser-7.17.0 [`ed44785`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/ed44785c58d15f50bd3d0846c8290cad6d42d650) 95 | - Merge pull request #73 from DIY0R/dependabot/npm_and_yarn/electron-to-chromium-1.5.0 [`12228e1`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/12228e1adb6bf4e86876acc119b0cdf8300f05b5) 96 | - Merge pull request #69 from DIY0R/dependabot/npm_and_yarn/import-local-3.2.0 [`9cf5b2f`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/9cf5b2fea8a10ef68879119c02aee735785050bf) 97 | - fix: handle unannounced route [`b5ed80b`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/b5ed80bbd59d540c728afcc61ccd600ddc46f95e) 98 | 99 | #### [0.22.48](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.22.17...0.22.48) 100 | 101 | > 22 July 2024 102 | 103 | - refact: change the name to serialize [`f3bb5fc`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/f3bb5fcef17785fe759c3dd24fddeb5c66d092af) 104 | - doc: contents about SerDes [`b888612`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/b8886128920e4ada679c8ff1a7d0f6d35fd315c2) 105 | - doc: 0.22.x [`c39e3fd`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/c39e3fd84823a35b1e8908cb5953b18f449c0e83) 106 | - doc: 0.22.x [`aaba57c`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/aaba57c28a4d5517ad707904934a254939a3ae4f) 107 | - doc: divided into subtopics Initialization and RmqGlobalService [`43122a0`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/43122a0ea5df867476b58ccb3169aa0fdd54bad6) 108 | - refact: correct typo [`48fe654`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/48fe654d7d0352ceec4bf0198771b12d01b3634d) 109 | - refact: scan only for queue [`c5109a3`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/c5109a370d10d2644c91e8a5202fc2e424efe4e7) 110 | - doc: correct typo [`257c304`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/257c3045ed8d2dca63b18802cbb768dce1075ee3) 111 | - Merge pull request #64 from DIY0R/dependabot/npm_and_yarn/is-core-module-2.15.0 [`5bc542f`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/5bc542fcde6df21acebd48dbdf779a12b4ce75ca) 112 | - merge pull request #61 from DIY0R/middleware [`ee82466`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/ee82466828800f2517f6a269d5fc757cc7d903d8) 113 | 114 | #### [0.22.17](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.21.9...0.22.17) 115 | 116 | > 18 July 2024 117 | 118 | - feat: middleware [`60cf316`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/60cf3164d959095d0f191f7dc251af4c789e2193) 119 | - test: middleware [`dcec4e6`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/dcec4e68100138c5bfb55c59791dd748bab6d53e) 120 | - refact: correct typo [`6b9235a`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/6b9235a63af1d6faa6176ee915d68c7533168737) 121 | - chod: husky update [`55f5009`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/55f5009063607d93e2ab3e4a59c56452af37d7d4) 122 | - fix: interceptor execution sequence [`7ad2b1b`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/7ad2b1b05eaf09faed82484ac4b0eecb1ea1d0ae) 123 | - refact: change array [`568a185`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/568a1856ec4707d6feb4d006b06da3399e200afa) 124 | - Merge pull request #62 from DIY0R/dependabot/npm_and_yarn/husky-9.1.0 [`837d228`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/837d2280fab6d817653e8a03f93404f2bb2c2aec) 125 | - Merge pull request #63 from DIY0R/dependabot/npm_and_yarn/eslint-plugin-prettier-5.2.1 [`16f29e2`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/16f29e2b629b7d89d05a3e612c4a5cc44a62d66a) 126 | - fix: async get chanel [`7b7fb2b`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/7b7fb2b3e11d26851ef4d9338491c74ca1e7c680) 127 | - Merge pull request #60 from DIY0R/dependabot/npm_and_yarn/release-it-17.6.0 [`8023c18`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/8023c183bb55c384e2ecd22fd23e8051d0d9f709) 128 | 129 | #### [0.21.9](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.21.4...0.21.9) 130 | 131 | > 14 July 2024 132 | 133 | - refact: catch error [`40f6c31`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/40f6c31a1ed77ba4991e3cdf87909687d1700daf) 134 | - merge pull request #59 from current_module [`7a668d3`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/7a668d3b2f21f61795889615d6981ce89139984b) 135 | - refact: change name and move token [`025b9b7`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/025b9b76c6e4ba324d2c947a2745a2284e6dc28e) 136 | 137 | #### [0.21.4](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.19.7...0.21.4) 138 | 139 | > 13 July 2024 140 | 141 | - merge pull request #58 from interceptor [`a8105e7`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/a8105e7d89f3a4334e028642223a58d631a7475a) 142 | - feat: decorator that allows Interceptor to be applied to specific endpoints [`64cdc34`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/64cdc348d561d3cef2bb2d2a4fa9c91842f4e1f5) 143 | - test: interceptors [`4342d24`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/4342d2413741719c738bc2c79b5e5a381db6191e) 144 | - feat: interceptor on individual modules [`0eb6802`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/0eb6802a09e3b19ec987950aa38e9a5a18f1478a) 145 | - Merge pull request #56 from DIY0R/dependabot/npm_and_yarn/jridgewell/sourcemap-codec-1.5.0 [`7c18f8d`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/7c18f8d9169df4f6383c652582e2b6d3b34a49ef) 146 | - Merge pull request #57 from DIY0R/dependabot/npm_and_yarn/jackspeak-3.4.3 [`54cb44e`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/54cb44e6c8b9e14af1553f550284fb0c1b5838ae) 147 | 148 | #### [0.19.7](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.19.4...0.19.7) 149 | 150 | > 10 July 2024 151 | 152 | - chore: bump @typescript-eslint/parser from 7.15.0 to 7.16.0 [`#53`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/pull/53) 153 | - refact: interface and remove unused [`197d9a6`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/197d9a66b58bb300c7811c5a7423406e4136d08f) 154 | - fix: skip check non objects [`812a029`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/812a0292b3259ade9a25935ee783e3c98ef972a8) 155 | - Merge pull request #51 from DIY0R/dependabot/npm_and_yarn/jackspeak-3.4.2 [`4fb24eb`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/4fb24eb0ec0dd01afcbdb4f4f9c021fe01935347) 156 | - Merge pull request #52 from DIY0R/dependabot/npm_and_yarn/esquery-1.6.0 [`a6c07a3`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/a6c07a3f0ab7b4a379ad4b568d41749253df58aa) 157 | - Merge pull request #54 from DIY0R/dependabot/npm_and_yarn/release-it-17.5.0 [`d2211cb`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/d2211cb64c4553cc53ffc3505f6595d6b414e466) 158 | - Merge pull request #55 from DIY0R/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-7.16.0 [`7d9026d`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/7d9026dba68d8e5afaf5a1914f453c04e7a499bc) 159 | 160 | #### [0.19.4](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.18.13...0.19.4) 161 | 162 | > 9 July 2024 163 | 164 | - Merge pull request #49 from DIY0R/dependabot/npm_and_yarn/ts-jest-29.2.0 [`f3a9fb7`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/f3a9fb76b756d93ea513da2638bae2ac02c2a633) 165 | - merge pull request from 'serdes' [`3deb065`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/3deb065f2709f2016689d24bf0f8e0d8244bff70) 166 | - refact: scaner [`9394df5`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/9394df5c67ae37d2f6dc3fe87db2bb459fcdd336) 167 | - feat: serdes on parent class [`31f285c`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/31f285c33dd3367638666c138062b20ed66071bf) 168 | - refact: types [`4c9017e`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/4c9017e39fee53912dfb8381339d1215d8257930) 169 | - test: SerDes on parent class [`36a32fa`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/36a32fa57b45eb36734f40e605537e294b0b199f) 170 | 171 | #### [0.18.13](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.18.12...0.18.13) 172 | 173 | > 8 July 2024 174 | 175 | #### [0.18.12](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.18.10...0.18.12) 176 | 177 | > 8 July 2024 178 | 179 | - fix: optional param [`962ab94`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/962ab94b95281dad3d2f5d75a1f292e8a07eca50) 180 | - refact: catch error [`cb532e5`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/cb532e57243414d75335f9cf9c08dd22010d2cff) 181 | 182 | #### [0.18.10](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.18.9...0.18.10) 183 | 184 | > 8 July 2024 185 | 186 | - fix: build command [`ac1ae80`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/ac1ae8091f3e4d3293c1cecd112d4aab9f398d8e) 187 | 188 | #### [0.18.9](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.18.8...0.18.9) 189 | 190 | > 8 July 2024 191 | 192 | - fix: add build command [`d969dab`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/d969dabfa604b4dce4b23daefce8736ea467124e) 193 | 194 | #### [0.18.8](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.18.7...0.18.8) 195 | 196 | > 8 July 2024 197 | 198 | - fix: build dist file [`62fa372`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/62fa3721eca03cbd839ee19879b30daf9c47af2b) 199 | 200 | #### [0.18.7](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.18.6...0.18.7) 201 | 202 | > 8 July 2024 203 | 204 | #### [0.18.6](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.18.5...0.18.6) 205 | 206 | > 7 July 2024 207 | 208 | - test: mix pattern [`c329369`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/c329369eff0b06598d5fefa3ddbcd3f45d47113b) 209 | 210 | #### [0.18.5](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.18.4...0.18.5) 211 | 212 | > 7 July 2024 213 | 214 | #### [0.18.4](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.18.3...0.18.4) 215 | 216 | > 7 July 2024 217 | 218 | #### [0.18.3](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.18.2...0.18.3) 219 | 220 | > 7 July 2024 221 | 222 | - fix: topic pattern checker function [`6544bab`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/6544bab1d22b5b893072e6ee7f4cf44485efe210) 223 | 224 | #### [0.18.2](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.13.5...0.18.2) 225 | 226 | > 7 July 2024 227 | 228 | - merge pull request #48 from DIY0R/serdes [`70b41b1`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/70b41b16749297685bfe7cecaf4b4d1f197271e8) 229 | - feat: decorator SerDes [`df175e8`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/df175e88c19020b6b78a04f5172750eb50c98bf8) 230 | - feat: default SerDas [`80216b7`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/80216b7063a7d97384d2478fe9da8dee21169d99) 231 | - test: serdes decorator check [`d6b1f4c`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/d6b1f4c355d45eb77cef88664cdde56a049510c4) 232 | - feat: determine SerDes object [`86b6aa2`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/86b6aa2c953847b1e4710eabd7a3841466c2b8ff) 233 | - test: serDas object [`1f06cf9`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/1f06cf9c79f58ee89b80dde6dacfc4547235afac) 234 | 235 | #### [0.13.5](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.13.4...0.13.5) 236 | 237 | > 6 July 2024 238 | 239 | - docs: change name [`5251ad5`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/5251ad5a1f09af12ad57827bc1ebd1ad80ae6180) 240 | 241 | #### [0.13.4](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.13.3...0.13.4) 242 | 243 | > 6 July 2024 244 | 245 | #### [0.13.3](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.13.2...0.13.3) 246 | 247 | > 6 July 2024 248 | 249 | - ci: change register [`7139246`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/713924629629e9f34f4c819d8f421b2323c76a16) 250 | 251 | #### [0.13.2](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.11.4...0.13.2) 252 | 253 | > 5 July 2024 254 | 255 | - Merge pull request #44 from DIY0R/dependabot/npm_and_yarn/update-browserslist-db-1.1.0 [`ef0ff60`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/ef0ff60bb1ca9accfd0e12cf8303693c04b410ae) 256 | - docs: base doc [`59613f6`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/59613f6f0950f761004ab1e79f5131b963d192e8) 257 | - fix: socket options [`7f0ccab`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/7f0ccabcfb16d5b75ac2ae6436051b391e5cfe63) 258 | - fix: async check notify [`789d3f6`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/789d3f685e906ed5c4e181f885ce4ade5bce7ebe) 259 | 260 | #### [0.11.4](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.9.2...0.11.4) 261 | 262 | > 4 July 2024 263 | 264 | - feat: select basic channel type [`67c6e51`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/67c6e51a0466db324c07b9435eee3cda6ee84c27) 265 | - refactor: exact status message [`5b6d2f2`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/5b6d2f2653b8c36d3971d7565dbc8a25d735c325) 266 | - refactor: send method [`1be6510`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/1be6510196c683ca47ac9e13c0d2ddfee74a6f00) 267 | - refactor: initialization check [`f4a3f21`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/f4a3f21506870d07232066da18d087498ad940c7) 268 | - feat: access to the channel [`38d4f06`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/38d4f06a5599677bce3fca95211d8d28643917a9) 269 | - fix: default answer [`79a2d84`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/79a2d84c8f9606b52f75c7aac1fd174f1072df19) 270 | - Merge pull request #41 from DIY0R/dependabot/npm_and_yarn/typescript-eslint/parser-7.15.0 [`58742db`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/58742dbeac3d46cbe43c5df4d91affc55f361c48) 271 | - Merge pull request #42 from DIY0R/dependabot/npm_and_yarn/nestjs/cli-10.4.0 [`2dff023`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/2dff023103159192cde68674de9b0f52af167cc4) 272 | - Merge pull request #43 from DIY0R/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-7.15.0 [`953f84f`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/953f84f42c160b49f4a3d9c5acd11ca2af725def) 273 | 274 | #### [0.9.2](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.8.2...0.9.2) 275 | 276 | > 3 July 2024 277 | 278 | - test: auto ack [`afd2e33`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/afd2e3346162c225ffb84f34aebee01bf80cb314) 279 | - feat: consum non route message [`eaacb2f`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/eaacb2ffbdcad88b809798ef4be24de8df9a3eaa) 280 | - refactor: consum fucntion type [`b93e7be`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/b93e7be13e6f5dd0400f414b69accf62efc4494e) 281 | 282 | #### [0.8.2](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.7.0...0.8.2) 283 | 284 | > 2 July 2024 285 | 286 | - test: send to queue [`029298a`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/029298a09052f3fbc4c569d3b670e29e21bb4e95) 287 | - feat: send to queue [`ed05d58`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/ed05d583981b07b5d6506d9650db1b5effef0176) 288 | - fix: ack message [`4644495`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/4644495115352f84507ceaf93ebe9924a5bdf24e) 289 | 290 | #### [0.7.0](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.6.0...0.7.0) 291 | 292 | > 1 July 2024 293 | 294 | - feat: specify a URL or object on connection param [`bdff569`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/bdff5695c00c1745859dd383ed0b61c133d9da73) 295 | 296 | #### [0.6.0](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.5.4...0.6.0) 297 | 298 | > 1 July 2024 299 | 300 | - feat: identify socketOptions [`213af0c`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/213af0c21a0d74994ca40ee88f61e0a6d5b85646) 301 | - Merge pull request #39 from DIY0R/dependabot/npm_and_yarn/eslint-community/regexpp-4.11.0 [`349c620`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/349c620232e243b330ec70020e5c92587395fe31) 302 | 303 | #### [0.5.4](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.4.1...0.5.4) 304 | 305 | > 30 June 2024 306 | 307 | - merge pull request 40 from DIY0R/notify [`b94b2e1`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/b94b2e10f39242092e11efc11fbc808395b9f92d) 308 | - feat: global notify [`926168d`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/926168dcf5a692a9bd222b6ae6775eafffbab0a4) 309 | - test: global notify [`fd5f8b9`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/fd5f8b9f5cd34a70429a9013664b32357fd30b99) 310 | - fix: initialization check [`cb730c2`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/cb730c24f01453e9b78f178533b38ac8b6e026bc) 311 | 312 | #### [0.4.1](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.3.1...0.4.1) 313 | 314 | > 30 June 2024 315 | 316 | - feat: global rpc send [`a354071`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/a354071a91619bedcb2965fe0375adc9486f2c1d) 317 | - feat: can give consum options [`1752fe0`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/1752fe00285f8a7237e11480481a6af3c694556f) 318 | - test: global rpc send [`bc16127`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/bc161275d143cc0500e151c11978f151959c8740) 319 | 320 | #### [0.3.1](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.3.0...0.3.1) 321 | 322 | > 29 June 2024 323 | 324 | - docs: base documentation [`7453983`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/7453983a4d76e78e6277f6a59cdf75726d7a8080) 325 | - refactor: cahnge Interfaces,impots [`a91d92f`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/a91d92fe74b4a6ecb53efcff679855a78ac77bc7) 326 | 327 | #### [0.3.0](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.2.2...0.3.0) 328 | 329 | > 28 June 2024 330 | 331 | - feat: topic support patterns [`acb08cc`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/acb08ccd933ef56cdf6364af76183fe8b3cc7d89) 332 | - test: topic patterns [`cea9b37`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/cea9b3701d9e926eaf35d1ca1d924d310e533b44) 333 | 334 | #### [0.2.2](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.2.1...0.2.2) 335 | 336 | > 27 June 2024 337 | 338 | - test: cant find route [`fd05b8e`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/fd05b8eb939e5465bde34d21738387b6d0d6b292) 339 | - fix: cant find route [`6150673`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/6150673fbeba86bafd69ae790800155d7dd96a23) 340 | 341 | #### [0.2.1](https://github.com/DIY0R/nestjs-rabbitmq-bridge/compare/0.2.0...0.2.1) 342 | 343 | > 27 June 2024 344 | 345 | - ci: pre build dist file [`ed69e51`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/ed69e51f3b5b5d8ed01b449370d8b62597455f53) 346 | 347 | #### 0.2.0 348 | 349 | > 27 June 2024 350 | 351 | - chore: bump typescript from 5.4.5 to 5.5.2 [`#30`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/pull/30) 352 | - feat: add able connetion,asserts [`#12`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/pull/12) 353 | - fix: change prefix [`#9`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/pull/9) 354 | - chore: bump prettier from 3.3.1 to 3.3.2 [`#7`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/pull/7) 355 | - chore: bump husky from 8.0.3 to 9.0.11 [`#6`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/pull/6) 356 | - Update README.md [`#2`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/pull/2) 357 | - feat: environment [`7cc6c06`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/7cc6c0653236e9ba1a911172bed73b5096021896) 358 | - ci: set up release [`789a36e`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/789a36e1a977d53002dab7fa6a0ae3e92fd3e884) 359 | - feat: add checker commit [`eb21db1`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/eb21db1ef43591e8efdc6dc57503715e32ff0531) 360 | - feat: add commit checker [`47d447f`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/47d447f3f8e4ce85e37bd83da8a6bbba6d7d7046) 361 | - feat: base RPC, logger [`44e4674`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/44e467477469dc1dd6f7241c833ee6e5d7eccf33) 362 | - feat: discover meta tags, auto binding [`41d0fc8`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/41d0fc8eeb220f12337decb99b6797b4681c423a) 363 | - feat: decorator definitions [`369f4ae`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/369f4aecad1e94a47e58486c5c11a0fdb44f088b) 364 | - feat: health check and events connection [`58fbfce`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/58fbfcea4787307116fd129fd1cbe201bbbec7de) 365 | - test: base test [`77a97c8`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/77a97c8012623e975fc83731be62ff0ce7622497) 366 | - test: rpc checker [`cb592ae`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/cb592aefde11ad6ccc6b5bbf34a4f91f91a818aa) 367 | - feat: create 2 chanel [`7cd001e`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/7cd001e4a6dec069de4066b6c5e4596d58d3c349) 368 | - fix: main queue will create in base chanel [`bbf8f67`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/bbf8f6715f6323353ddba2d72c33c2bf208dcf23) 369 | - fix: linter [`bccb83b`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/bccb83bdde61a825855555d144ac049f1bb1803b) 370 | - feat: create main.yml [`9bd4c90`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/9bd4c90a7aaa7143d4900a685a6ba6020f2a9b6a) 371 | - feat: notification option [`1879f2e`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/1879f2e3c7d28b719fca53515ce0430d607f3845) 372 | - refactor: separation of concerns [`a3884b6`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/a3884b670545c5085d740d80176844ba6a9ed3a4) 373 | - feat: asynchronous announcement feature [`fdbafb2`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/fdbafb2bbde3e354872048eec109283bd1dce6b5) 374 | - test: helth check,send felled [`8496f48`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/8496f48b2e7c0cbfadaf94506bc0fa93b0a0ec48) 375 | - test: add feature module [`c83f140`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/c83f140d92398dcc62ab769c48a9c0f6d44295af) 376 | - ci: release [`bcd411b`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/bcd411b9a3449f433e9d90dad500058e62d01a81) 377 | - Update main.yml [`188533f`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/188533f693c42f375e10461d0c846339dd2787b1) 378 | - ci: change rules [`1466a45`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/1466a45b8c5f108ada8e3d4f39b42e128f070faa) 379 | - test: set up jest [`af5a92a`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/af5a92a5e04d05dce34e71a7c85aa97d51c474d6) 380 | - Create package.json [`2cdaa08`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/2cdaa083e8e19e3cd86358628432467f69b20c97) 381 | - feat: commitlint.config.js [`5178391`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/517839198a6f5b0fc79cc100073bafb203dce6e1) 382 | - refactor: shorten the function [`ae60b24`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/ae60b24d2cc23b000025dac08ed6dd7624a77703) 383 | - feat(dependabot): add dependabot configuration [`f024783`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/f024783f47f5ce27ee6c495c02f4e30ed6bcd89e) 384 | - refactor: no need export providers [`454b892`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/454b89250ccbf13a9fa3f70d80d8464c2e32cf0c) 385 | - fix: updating conflicting versions [`67c35e3`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/67c35e3dd128f52c4f8cb74eae6d481215f02c2e) 386 | - ci: npm publish [`bfe82ce`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/bfe82ceffa05c6ae05cff66e4ee0161d80bdb762) 387 | - test: pre test [`90f19bc`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/90f19bce691fe8cd38af7dcd9fc96b4a5b383367) 388 | - Merge pull request #35 from DIY0R/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-7.14.1 [`238b9b2`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/238b9b2cdafe725e6a4a05e312532b2c596bb4d7) 389 | - Merge pull request #36 from DIY0R/dependabot/npm_and_yarn/typescript-eslint/parser-7.14.1 [`e78f116`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/e78f1164b6ff37ce3fdbeeb40a8164583bd0c36f) 390 | - Merge pull request #33 from DIY0R/dependabot/npm_and_yarn/v8-to-istanbul-9.3.0 [`ccb49d6`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/ccb49d651ee57ded92d555394f5bbb62a8ea16ec) 391 | - merge pull request from DIY0R/helth-check [`8dc16f6`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/8dc16f6a1e44ae7582c6c3b4abe8115e1163eabf) 392 | - merge pull request from DIY0R/target-module-scaner [`270a419`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/270a4198f18eb377b48fb5624eb3ea0494135883) 393 | - fix: update include file [`aa57cbc`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/aa57cbc5821010d8f80a2075ae98f8c234891d94) 394 | - ci: add check branches [`ee5ce3d`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/ee5ce3d8f767dba18532e0edde0cef81a75d71dc) 395 | - merge pull request from DIY0R/test [`6831dd4`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/6831dd48ccb02fd99e32d26b83de29a78a2d0e10) 396 | - merge pull request from DIY0R/dependabot/npm_and_yarn/is-core-module-2.14.0 [`ec4d7e3`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/ec4d7e3421851970d8fedbd43303bf67a933946a) 397 | - merge pull request from DIY0R/dependabot/npm_and_yarn/acorn-8.12.0 [`f06fe0c`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/f06fe0c093c6dc8342661c8eb0fd136d386e08af) 398 | - merge pull request from DIY0R/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-7.13.1 [`3f4d8bd`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/3f4d8bda1aafe23da0184370a788dbdb0015b7ef) 399 | - merge pull request from DIY0R/feat-notify [`40e3700`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/40e370089338100250c47cb57c4073bd66bdfb9b) 400 | - merge pull request from DIY0R/conflicting-versions [`41a7a3d`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/41a7a3d27c7726f47e78349c4054f94e3ce1ead4) 401 | - merge pull request DIY0R/fix_chanel-refactor [`7f17fd8`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/7f17fd81b3e77f8d2caefdc805ba64fa6eb5c175) 402 | - merge pull request from DIY0R/dependabot/npm_and_yarn/foreground-child-3.2.0 [`1b9e7e9`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/1b9e7e9c62a2de31794ed1d8810ac028b3453558) 403 | - merge pull request from DIY0R/baseRPC-logger [`e15cf16`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/e15cf169fb72e573cebf55ee1e015dcb03ee0ea0) 404 | - merge request from discovery-meta-tags [`df51813`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/df51813e1b81b3162c326035e968e274af536aad) 405 | - Initial commit [`76954c5`](https://github.com/DIY0R/nestjs-rabbitmq-bridge/commit/76954c5fb7b778f1bf0453a7d7caaef6f697433a) 406 | --------------------------------------------------------------------------------