├── .env.dist
├── .github
└── dependabot.yml
├── .gitignore
├── .npmignore
├── .prettierignore
├── .prettierrc
├── .travis.yml
├── LICENSE
├── README.md
├── docker-compose.yml
├── package.json
├── src
├── __tests__
│ ├── __stubs__
│ │ └── config
│ │ │ └── amqp.ts
│ ├── amqp.inject.decorator.spec.ts
│ ├── amqp.module.spec.ts
│ ├── amqp.retry.spec.ts
│ └── setup.js
├── amqp.constants.ts
├── amqp.module.ts
├── decorators
│ └── index.ts
├── index.ts
├── interfaces
│ ├── amqp.async.options.ts
│ ├── amqp.options.object.interface.ts
│ ├── amqp.options.ts
│ └── index.ts
└── utils
│ ├── create.tokens.ts
│ ├── index.ts
│ └── retry.ts
├── tsconfig.json
└── yarn.lock
/.env.dist:
--------------------------------------------------------------------------------
1 | HOST=localhost
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | open-pull-requests-limit: 10
8 | ignore:
9 | - dependency-name: "@types/node"
10 | versions:
11 | - 14.14.22
12 | - 14.14.24
13 | - 14.14.25
14 | - 14.14.26
15 | - 14.14.28
16 | - 14.14.32
17 | - 14.14.33
18 | - 14.14.34
19 | - 14.14.35
20 | - 14.14.36
21 | - 14.14.37
22 | - 14.14.39
23 | - 14.14.41
24 | - 15.0.0
25 | - dependency-name: y18n
26 | versions:
27 | - 4.0.1
28 | - 4.0.2
29 | - dependency-name: "@types/jest"
30 | versions:
31 | - 26.0.20
32 | - 26.0.21
33 | - 26.0.22
34 | - dependency-name: typescript
35 | versions:
36 | - 4.1.5
37 | - 4.2.2
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
63 | /dist
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /src
2 | tsconfig.json
3 | docker-compose.yml
4 | .travis.yml
5 | .env.dist
6 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | dist/
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "printWidth": 80,
4 | "proseWrap": "always",
5 | "tabWidth": 2,
6 | "useTabs": false,
7 | "trailingComma": "all",
8 | "bracketSpacing": true,
9 | "jsxBracketSameLine": false,
10 | "semi": true
11 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: required
2 |
3 | language: node_js
4 | node_js:
5 | - "8"
6 | - "10"
7 | - "node"
8 | services:
9 | - docker
10 | before_install:
11 | - docker-compose up -d --build --force-recreate
12 | - npm i -g npm@latest
13 | - npm i -g yarn
14 | - cp .env.dist .env
15 | install:
16 | - yarn
17 | script:
18 | - yarn run test
19 | after_success: yarn run coveralls
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Ashleigh Simonelli
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |

2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Nestjs Amqp
13 |
14 | An AMQP connection service for NestJS.
15 |
16 | Using the AMQPlib for node package.
17 |
18 | This package was intented to be used in execution content and provides a basic AMQPlib connection via the providers to allow developers to develop their amqp queue consumers and publishers. For microservice transport; check out the docs for rabbitMQ.
19 |
20 | ## Install
21 |
22 | ```bash
23 | $ yarn add nestjs-amqp
24 | $ yarn add -D @types/amqplib
25 | ```
26 |
27 | ## Basic usage
28 |
29 | ```ts
30 | import {Module} from '@nestjs/common';
31 | import {AmqpModule} from 'nestjs-amqp';
32 |
33 | @Module({
34 | imports: [AmqpModule.forRoot({
35 | name: 'rabbitmq',
36 | hostname: 'localhost',
37 | port: 5672,
38 | username: 'test',
39 | password: 'test',
40 | })],
41 | })
42 | export default class AppModule {}
43 |
44 | ```
45 |
46 | ## Advanced usage
47 | ### Usage with nestjs-config
48 |
49 | ```ts
50 | import {Module} from '@nestjs/common';
51 | import {AmqpModule} from 'nestjs-amqp';
52 | import {ConfigModule, ConfigService} from 'nestjs-config';
53 | import * as path from 'path';
54 |
55 | @Module({
56 | imports: [
57 | ConfigModule.load(path.resolve(__dirname, 'config', '**/*.ts')),
58 | AmqpModule.forRootAsync({
59 | useFactory: (config: ConfigService) => config.get('amqp'),
60 | inject: [ConfigService],
61 | }),
62 | ],
63 | })
64 | export default class AppModule {}
65 |
66 | //src/config/amqp.ts
67 | export default {
68 | name: 'rabbitmq',
69 | hostname: process.env.AMQP_HOST,
70 | port: process.env.AMQP_PORT,
71 | username: process.env.USERNAME,
72 | password: process.env.PASSWORD,
73 | }
74 | ```
75 | > Unfortunately multiple connections are unavailable when using the `forRootAsync` method.
76 |
77 | ### Usage in custom provider
78 | It is possible to inject the AmqpConnection in a factory of a custom provider if one needs such capability.
79 |
80 |
81 | ```ts
82 | import { Connection as AmqpConnection } from 'amqplib';
83 | import {ConfigService} from 'nestjs-config';
84 | import {createConnectionToken} from 'nestjs-amqp/utils';
85 |
86 | export const queueServiceProvider = {
87 | provider: 'QueueService',
88 | useFactory: (amqp: AmqpConnection, configService: ConfigService) => new QueueService(amqp, config.get('queue')),
89 | inject: [createConnectionToken('default'), ConfigService],
90 | }
91 | ```
92 |
93 | It's also possible to give your connections names, if you have done so then use the name of your connection instead of ``default``.
94 | ### Connection Decorators
95 |
96 | ```ts
97 | import {Module} from '@nestjs/common';
98 | import {AmqpModule} from 'nestjs-amqp';
99 |
100 | @Module({
101 | imports: [AmqpModule.forRoot([
102 | {
103 | hostname: 'test:test@localhost',
104 | },
105 | {
106 | username: 'test',
107 | password: 'test',
108 | hostname: 'localhost',
109 | port: 5672,
110 | protocol: 'amqps',
111 | name: 'test',
112 | }
113 | ])],
114 | })
115 | export default class ExecutionModule {
116 | }
117 | ```
118 |
119 | ```ts
120 | import {Injectable} from '@nestjs/common';
121 | import {InjectAmqpConnection} from 'nestjs-amqp';
122 | import {Connection} from 'amqplib';
123 |
124 | @Injectable()
125 | export default class TestService {
126 | constructor(
127 | @InjectAmqpConnection('test')
128 | private readonly connectionTest: Connection, //gets connection with name 'test' defined in module
129 | @InjectAmqpConnection(0)
130 | private readonly connection0: Connection, //gets first defined connection without a name
131 | ) {}
132 | }
133 | ```
134 | > Use InjectAmqpConnection without a parameter for default connection
135 |
136 | ### Example publish
137 |
138 | ```ts
139 | import {Injectable, Logger} from '@nestjs/common';
140 | import {InjectAmqpConnection} from 'nestjs-amqp';
141 | import {Connection} from 'amqplib';
142 |
143 | @Injectable()
144 | export default class TestProvider {
145 | constructor(
146 | @InjectAmqpConnection()
147 | private readonly amqp: Connection,
148 | ) {}
149 | async publish(message: string) {
150 | await this.amqp.createChannel((err, channel) => {
151 | if (err != null) {
152 | Logger.alert(err, 'TestProvider');
153 | }
154 | channel.assertQueue('test_queue_name');
155 | channel.sendToQueue('test_queue_name', message);
156 | });
157 | }
158 | }
159 | ```
160 | More information and examples about amqplib can be found [here](https://www.npmjs.com/package/amqplib).
161 |
162 | ## Available Options
163 |
164 | Name | For | Default
165 | --- | --- | ---
166 | hostname | The host url for the connection | `localhost`
167 | port | The port of the amqp host | `5672`
168 | name | The name of the connection | `default` or the array key index `[0]`
169 | retrys | The amount of retry attempts before surrender | 3
170 | retryDelay | The amount of milliseconds to wait before attempting retry | 3000
171 | protocol | The protocol for the connection | `amqp`
172 | username | The username for the connection |
173 | password | The password for the connection |
174 | locale | The desired locale for error messages | `en_US`
175 | frameMax | The size in bytes of the maximum frame allowed over the connection | 0
176 | heartbeat | The period of the connection heartbeat in seconds | 0
177 | vhost | What VHost shall be used | `/`
178 |
179 | ## Testing this package
180 |
181 | In order to test first you need to start the rabbitmq container. We've provided a `docker-compose` file to make this easier.
182 |
183 | ```bash
184 | $ docker-compose up -d
185 | $ yarn test
186 | ```
187 | > Navigate to localhost:15672 for rabbitmq manager, username and password are both `guest`
188 |
189 | > If you're using docker-machine or a VM then change the env for `HOST` in the `.env` file or create one using the provided `.env.dist` file.
190 |
191 | ## Future implementation
192 |
193 | > WARNING: The below examples have not been implemented
194 |
195 | So far this package manages multiple AMQP connections using the nestjs container and injects them into other providers.
196 | Alternatively I'd like to implement something like this:
197 |
198 | ```ts
199 | import {Injectable} from '@nestjs/common';
200 | import {
201 | AmqpConnection,
202 | Consume,
203 | Publish,
204 | Message,
205 | } from 'nestjs-amqp';
206 |
207 | @Injectable()
208 | @AmqpConnection()
209 | export default class MyAmqpService {
210 |
211 | @Consume("queue_name", {
212 | noAck: true,
213 | })
214 | async listen(@Message message) {
215 | console.log('Message received', message);
216 |
217 | //send a message back
218 | this.publish();
219 | }
220 |
221 | @Publish("queue_name")
222 | async publish() {
223 | return "Send this to 'queue queue_name'";
224 | }
225 | }
226 | ```
227 |
228 | Then using executable context
229 |
230 | ```ts
231 |
232 | import { NestFactory } from '@nestjs/core';
233 | import QueueModule, { MyAmqpService } from './queue';
234 |
235 | async function bootstrap() {
236 | const app = await NestFactory.create(QueueModule);
237 | const event = app.get(MyAmqpService);
238 |
239 | await event.listen();
240 |
241 | }
242 | bootstrap();
243 |
244 | process.stdin.resume();
245 | ```
246 |
247 | Or something similar to the above is what I'd like to implement
248 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | rabbitmq:
4 | image: rabbitmq:3-management
5 | ports:
6 | - 15672:15672
7 | - 5672:5672
8 | secure-rabbitmq:
9 | image: rabbitmq:3-management
10 | ports:
11 | - 15673:15672
12 | - 5673:5672
13 | environment:
14 | RABBITMQ_DEFAULT_PASS: pass
15 | RABBITMQ_DEFAULT_USER: user
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nestjs-amqp",
3 | "description": "AMQP connection service for nestjs",
4 | "keywords": [
5 | "nestjs",
6 | "amqp"
7 | ],
8 | "main": "dist/index.js",
9 | "version": "0.2.2",
10 | "license": "MIT",
11 | "devDependencies": {
12 | "@nestjs/common": "^6.4.1",
13 | "@nestjs/core": "^6.4.1",
14 | "@nestjs/platform-express": "^7.0.0",
15 | "@nestjs/testing": "^7.0.0",
16 | "@types/amqplib": "^0.5.13",
17 | "@types/es6-promise": "^3.3.0",
18 | "@types/jest": "^27.0.2",
19 | "@types/node": "^15.12.4",
20 | "coveralls": "^3.0.2",
21 | "dotenv": "^10.0.0",
22 | "jest": "^24.9.0",
23 | "nestjs-config": "^1.2.3",
24 | "prettier": "^2.4.1",
25 | "ts-jest": "^24.0.2",
26 | "typescript": "^4.1.3"
27 | },
28 | "peerDependencies": {
29 | "@nestjs/common": "^5.1.0 || ^6.0.3",
30 | "@types/amqplib": "^0.5.13"
31 | },
32 | "dependencies": {
33 | "amqplib": "^0.7.0",
34 | "reflect-metadata": "^0.1.12",
35 | "rxjs": "^6.2.1"
36 | },
37 | "scripts": {
38 | "build": "rm -rf ./dist && tsc",
39 | "coverage": "jest --coverage",
40 | "coveralls": "yarn run coverage --coverageReporters=text-lcov | coveralls",
41 | "test": "jest",
42 | "format": "prettier **/**/*.ts --ignore-path ./.prettierignore --write && git status",
43 | "prepublish": "npm run format && npm run build"
44 | },
45 | "repository": {
46 | "type": "git",
47 | "url": "https://github.com/nestjs-community/nestjs-amqp.git"
48 | },
49 | "jest": {
50 | "moduleFileExtensions": [
51 | "js",
52 | "json",
53 | "ts"
54 | ],
55 | "rootDir": "src",
56 | "testRegex": ".spec.ts$",
57 | "transform": {
58 | "^.+\\.(t|j)s$": "ts-jest"
59 | },
60 | "coverageDirectory": "./coverage",
61 | "setupFiles": [
62 | "/__tests__/setup.js"
63 | ]
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/__tests__/__stubs__/config/amqp.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | hostname: process.env.HOST,
3 | port: 5672,
4 | };
--------------------------------------------------------------------------------
/src/__tests__/amqp.inject.decorator.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { Injectable } from '@nestjs/common';
3 | import { AmqpModule } from './../index';
4 | import { InjectAmqpConnection } from '../decorators';
5 | const ChannelModel = require('amqplib/lib/channel_model').ChannelModel;
6 |
7 | describe('InjectAmqpConnection', () => {
8 | it('Connection should inject', async () => {
9 | @Injectable()
10 | class TestProvider {
11 | constructor(@InjectAmqpConnection() private readonly connection) {}
12 |
13 | getConnection() {
14 | return this.connection;
15 | }
16 | }
17 |
18 | const module: TestingModule = await Test.createTestingModule({
19 | imports: [
20 | AmqpModule.forRoot({
21 | hostname: process.env.HOST,
22 | retrys: 1,
23 | }),
24 | ],
25 | providers: [TestProvider],
26 | }).compile();
27 |
28 | const app = module.createNestApplication();
29 | await app.init();
30 |
31 | const provider = module.get(TestProvider);
32 |
33 | expect(provider.getConnection()).toBeInstanceOf(ChannelModel);
34 | await app.close();
35 | });
36 |
37 | it('Connection should inject with name', async () => {
38 | @Injectable()
39 | class TestProvider {
40 | constructor(
41 | @InjectAmqpConnection('1') private readonly connection,
42 | @InjectAmqpConnection('0') private readonly connection0,
43 | ) {}
44 |
45 | getConnection() {
46 | return this.connection;
47 | }
48 |
49 | getConnection0() {
50 | return this.connection0;
51 | }
52 | }
53 |
54 | const module: TestingModule = await Test.createTestingModule({
55 | imports: [
56 | AmqpModule.forRoot([
57 | {
58 | hostname: process.env.HOST,
59 | retrys: 1,
60 | },
61 | {
62 | hostname: process.env.HOST,
63 | retrys: 1,
64 | },
65 | ]),
66 | ],
67 | providers: [TestProvider],
68 | }).compile();
69 |
70 | const app = module.createNestApplication();
71 | await app.init();
72 |
73 | const provider = module.get(TestProvider);
74 |
75 | expect(provider.getConnection()).toBeInstanceOf(ChannelModel);
76 | expect(provider.getConnection0()).toBeInstanceOf(ChannelModel);
77 | await app.close();
78 | });
79 | });
80 |
--------------------------------------------------------------------------------
/src/__tests__/amqp.module.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { AmqpModule } from './../index';
3 | import { createConnectionToken } from '../utils/create.tokens';
4 | import { Module } from '@nestjs/common';
5 | const ChannelModel = require('amqplib/lib/channel_model').ChannelModel;
6 | import { ConfigModule, ConfigService } from 'nestjs-config';
7 | import * as path from 'path';
8 | import { InjectAmqpConnection } from '../decorators';
9 |
10 | describe('AmqpModule', () => {
11 | it('Instace Amqp', async () => {
12 | const module: TestingModule = await Test.createTestingModule({
13 | imports: [
14 | AmqpModule.forRoot({
15 | name: 'instance',
16 | hostname: process.env.HOST,
17 | retrys: 1,
18 | }),
19 | ],
20 | }).compile();
21 |
22 | const app = module.createNestApplication();
23 | await app.init();
24 |
25 | const amqpModule = module.get(AmqpModule);
26 |
27 | expect(amqpModule).toBeInstanceOf(AmqpModule);
28 |
29 | await app.close();
30 | });
31 |
32 | it('Instace Amqp Connection provider', async () => {
33 | const module: TestingModule = await Test.createTestingModule({
34 | imports: [
35 | AmqpModule.forRoot({
36 | name: 'first',
37 | hostname: process.env.HOST,
38 | retrys: 1,
39 | }),
40 | ],
41 | }).compile();
42 |
43 | const app = module.createNestApplication();
44 | await app.init();
45 |
46 | const amqpConnection = module.get(createConnectionToken('first'));
47 |
48 | expect(amqpConnection).toBeInstanceOf(ChannelModel);
49 | await app.close();
50 | });
51 |
52 | it('Multiple connection options', async () => {
53 | const module: TestingModule = await Test.createTestingModule({
54 | imports: [
55 | AmqpModule.forRoot([
56 | {
57 | hostname: process.env.HOST,
58 | name: 'test',
59 | retrys: 1,
60 | },
61 | {
62 | hostname: process.env.HOST,
63 | retrys: 1,
64 | },
65 | ]),
66 | ],
67 | }).compile();
68 |
69 | const app = module.createNestApplication();
70 | await app.init();
71 |
72 | const amqpConnectionTest = module.get(createConnectionToken('test'));
73 | const amqpConnection1 = module.get(createConnectionToken('1'));
74 |
75 | expect(amqpConnectionTest).toBeInstanceOf(ChannelModel);
76 | expect(amqpConnection1).toBeInstanceOf(ChannelModel);
77 | await app.close();
78 | });
79 |
80 | it('Connection options', async () => {
81 | const module: TestingModule = await Test.createTestingModule({
82 | imports: [
83 | AmqpModule.forRoot({
84 | hostname: process.env.HOST,
85 | name: 'test2',
86 | port: 5673,
87 | username: 'user',
88 | password: 'pass',
89 | retrys: 1,
90 | }),
91 | ],
92 | }).compile();
93 |
94 | const app = module.createNestApplication();
95 | await app.init();
96 |
97 | const amqpConnectionTest = module.get(createConnectionToken('test2'));
98 |
99 | expect(amqpConnectionTest).toBeInstanceOf(ChannelModel);
100 | await app.close();
101 | });
102 |
103 | it('Connection available in submodule', async () => {
104 | @Module({
105 | imports: [AmqpModule.forFeature()],
106 | })
107 | class SubModule {}
108 |
109 | const module: TestingModule = await Test.createTestingModule({
110 | imports: [
111 | AmqpModule.forRoot({
112 | name: 'subModule',
113 | hostname: process.env.HOST,
114 | retrys: 1,
115 | }),
116 | SubModule,
117 | ],
118 | }).compile();
119 |
120 | const app = module.createNestApplication();
121 | await app.init();
122 |
123 | const provider = module
124 | .select(SubModule)
125 | .get(createConnectionToken('subModule'));
126 |
127 | expect(provider).toBeInstanceOf(ChannelModel);
128 | await app.close();
129 | });
130 |
131 | it('Connections should build with AmqpAsyncOptionsInterface', async () => {
132 | class TestProvider {
133 | constructor(@InjectAmqpConnection() private readonly amqp) {}
134 |
135 | getAmqp() {
136 | return this.amqp;
137 | }
138 | }
139 |
140 | const module: TestingModule = await Test.createTestingModule({
141 | imports: [
142 | ConfigModule.load(
143 | path.resolve(__dirname, '__stubs__', 'config', '*.ts'),
144 | ),
145 | AmqpModule.forRootAsync({
146 | useFactory: async config => config.get('amqp'),
147 | inject: [ConfigService],
148 | }),
149 | ],
150 | providers: [TestProvider],
151 | }).compile();
152 |
153 | const app = module.createNestApplication();
154 | await app.init();
155 |
156 | const provider = module.get(TestProvider);
157 |
158 | expect(provider.getAmqp()).toBeInstanceOf(ChannelModel);
159 | await app.close();
160 | });
161 | });
162 |
--------------------------------------------------------------------------------
/src/__tests__/amqp.retry.spec.ts:
--------------------------------------------------------------------------------
1 | import retry from '../utils/retry';
2 | import { from } from 'rxjs';
3 | import * as amqp from 'amqplib';
4 |
5 | jest.setTimeout(30000);
6 |
7 | describe('Amqp Retry', () => {
8 | it('retrys are called', async () => {
9 | try {
10 | const result = await from(
11 | amqp.connect({
12 | hostname: 'notathing',
13 | port: 3444,
14 | }),
15 | )
16 | .pipe(retry())
17 | .toPromise();
18 | expect(false).toBeTruthy();
19 | } catch (e) {
20 | expect(true).toBeTruthy();
21 | }
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/src/__tests__/setup.js:
--------------------------------------------------------------------------------
1 | var dotenv = require('dotenv');
2 | var path = require('path');
3 |
4 | dotenv.config({
5 | path: path.resolve(__dirname, '..', '..', '.env'),
6 | });
--------------------------------------------------------------------------------
/src/amqp.constants.ts:
--------------------------------------------------------------------------------
1 | export const AMQP_OPTIONS_PROVIDER = 'AMQP_OPTIONS_PROVIDER';
2 | export const AMQP_CONNECTION_PROVIDER = 'AMQP_CONNECTION_PROVIDER';
3 |
--------------------------------------------------------------------------------
/src/amqp.module.ts:
--------------------------------------------------------------------------------
1 | import {Module, DynamicModule, Provider, OnModuleDestroy} from '@nestjs/common';
2 | import { AmqpOptionsInterface, AmqpAsyncOptionsInterface, AmqpOptionsObjectInterface } from './interfaces';
3 | import { createConnectionToken, createOptionsToken } from './utils/create.tokens';
4 | import {from} from 'rxjs';
5 | import * as amqp from 'amqplib';
6 | import retry from './utils/retry';
7 | import { AMQP_OPTIONS_PROVIDER, } from './amqp.constants';
8 | import { ModuleRef } from '@nestjs/core';
9 |
10 | @Module({})
11 | export class AmqpModule implements OnModuleDestroy {
12 | private static connectionNames: string[] = [];
13 |
14 | constructor(private readonly moduleRef: ModuleRef) {}
15 |
16 | public static forRoot(options: AmqpOptionsInterface | AmqpOptionsInterface[]): DynamicModule {
17 |
18 | const optionsProviders: Provider[] = [];
19 | const connectionProviders: Provider[] = [];
20 |
21 | options = this.resolveOptions(options);
22 |
23 | optionsProviders.push(this.createOptionsProvider(options));
24 |
25 | options.forEach(options => {
26 | connectionProviders.push(this.createConnectionProvider(options));
27 | });
28 |
29 | return {
30 | module: AmqpModule,
31 | providers: [
32 | ...optionsProviders,
33 | ...connectionProviders,
34 | ],
35 | exports: connectionProviders,
36 | };
37 | }
38 |
39 | public static forFeature(): DynamicModule {
40 | return {
41 | module: AmqpModule,
42 | };
43 | }
44 |
45 | public static forRootAsync(options: AmqpAsyncOptionsInterface): DynamicModule {
46 |
47 | AmqpModule.connectionNames.push(createConnectionToken('default'));
48 |
49 | const connectionProviders = [
50 | {
51 | provide: createConnectionToken('default'),
52 | useFactory: async (config: AmqpOptionsInterface) => await from(amqp.connect(config)).pipe(retry(options.retrys, options.retryDelay)).toPromise(),
53 | inject: [createOptionsToken('default')],
54 | },
55 | ];
56 |
57 | return {
58 | module: AmqpModule,
59 | providers: [
60 | {
61 | provide: createOptionsToken('default'),
62 | useFactory: options.useFactory,
63 | inject: options.inject || [],
64 | },
65 | ...connectionProviders,
66 | ],
67 | exports: connectionProviders,
68 | };
69 | }
70 |
71 | private static createOptionsProvider(options: AmqpOptionsInterface[]): Provider {
72 | const optionsObject: AmqpOptionsObjectInterface = {};
73 |
74 | if (Array.isArray(options)) {
75 | options.forEach((options) => {
76 | optionsObject[options.name] = options;
77 | });
78 | }
79 |
80 | return {
81 | provide: AMQP_OPTIONS_PROVIDER,
82 | useValue: optionsObject,
83 | };
84 | }
85 |
86 | private static createConnectionProvider(options: AmqpOptionsInterface): Provider {
87 | AmqpModule.connectionNames.push(createConnectionToken(options.name));
88 | return {
89 | provide: createConnectionToken(options.name),
90 | //TODO resolve host url: do I need to? Seems to work aready? Just verify
91 | useFactory: async (config: AmqpOptionsInterface) => await from(amqp.connect(config[options.name ? options.name : 'default'])).pipe(retry(options.retrys, options.retryDelay)).toPromise(),
92 | inject: [AMQP_OPTIONS_PROVIDER],
93 | };
94 | }
95 |
96 | private static resolveOptions(options: AmqpOptionsInterface|AmqpOptionsInterface[]): AmqpOptionsInterface[] {
97 | if (!Array.isArray(options) && !options.hasOwnProperty('name')) options.name = 'default';
98 |
99 | if (!Array.isArray(options)) {
100 | options = [options];
101 | }
102 |
103 | options.forEach((options, key) => {
104 | if (!options.hasOwnProperty('name')) {
105 | options.name = key.toString();
106 | }
107 | });
108 |
109 | return options;
110 | }
111 |
112 | async onModuleDestroy(): Promise {
113 | AmqpModule.connectionNames.forEach(async connectionName => {
114 | const connection = this.moduleRef.get(connectionName);
115 | connection !== null && await connection.close();
116 | });
117 |
118 | AmqpModule.connectionNames = [];
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/decorators/index.ts:
--------------------------------------------------------------------------------
1 | import { createConnectionToken } from '../utils/create.tokens';
2 | import { Inject } from '@nestjs/common';
3 |
4 | export const InjectAmqpConnection = (name: string = 'default') => {
5 | return Inject(createConnectionToken(name));
6 | };
7 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './amqp.module';
2 | export * from './interfaces';
3 | export * from './decorators';
4 | export * from './utils';
5 |
--------------------------------------------------------------------------------
/src/interfaces/amqp.async.options.ts:
--------------------------------------------------------------------------------
1 | import { AmqpOptionsInterface } from './amqp.options';
2 |
3 | export interface AmqpAsyncOptionsInterface {
4 | inject?: any[];
5 | useFactory?: (
6 | ...args: any[]
7 | ) => Promise | AmqpOptionsInterface;
8 | retrys?: number;
9 | retryDelay?: number;
10 | }
11 |
--------------------------------------------------------------------------------
/src/interfaces/amqp.options.object.interface.ts:
--------------------------------------------------------------------------------
1 | import { AmqpAsyncOptionsInterface } from './amqp.async.options';
2 |
3 | export interface AmqpOptionsObjectInterface {
4 | [key: string]: AmqpAsyncOptionsInterface;
5 | }
6 |
--------------------------------------------------------------------------------
/src/interfaces/amqp.options.ts:
--------------------------------------------------------------------------------
1 | import { Options } from 'amqplib';
2 |
3 | export interface AmqpOptionsInterface extends Partial {
4 | name?: string;
5 | retrys?: number;
6 | retryDelay?: number;
7 | }
8 |
--------------------------------------------------------------------------------
/src/interfaces/index.ts:
--------------------------------------------------------------------------------
1 | export * from './amqp.options';
2 | export * from './amqp.async.options';
3 | export * from './amqp.options.object.interface';
4 |
--------------------------------------------------------------------------------
/src/utils/create.tokens.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AMQP_CONNECTION_PROVIDER,
3 | AMQP_OPTIONS_PROVIDER,
4 | } from '../amqp.constants';
5 |
6 | export const createOptionsToken = (name: string): string => {
7 | return `${AMQP_OPTIONS_PROVIDER}_${name}`;
8 | };
9 |
10 | export const createConnectionToken = (name: string): string => {
11 | return `${AMQP_CONNECTION_PROVIDER}_${name}`;
12 | };
13 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './create.tokens';
2 |
--------------------------------------------------------------------------------
/src/utils/retry.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from 'rxjs';
2 | import { retryWhen, scan, delay } from 'rxjs/operators';
3 | import { Logger } from '@nestjs/common';
4 |
5 | export default function retry(
6 | retryAttempts: number = 3,
7 | retryDelay: number = 3000,
8 | ): (source: Observable) => Observable {
9 | return (source: Observable) =>
10 | source.pipe(
11 | retryWhen(e =>
12 | e.pipe(
13 | scan((acc: number, error: Error) => {
14 | Logger.error(
15 | `Unable to connect to amqp server. Retrying`,
16 | error.stack,
17 | 'AmqpModule',
18 | );
19 | if (acc + 1 >= retryAttempts) {
20 | throw error;
21 | }
22 | return acc + 1;
23 | }, 0),
24 | delay(retryDelay),
25 | ),
26 | ),
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "declaration": true,
5 | "noImplicitAny": false,
6 | "noUnusedLocals": false,
7 | "removeComments": false,
8 | "noLib": false,
9 | "emitDecoratorMetadata": true,
10 | "experimentalDecorators": true,
11 | "target": "es6",
12 | "sourceMap": false,
13 | "allowJs": false,
14 | "rootDir": "./src",
15 | "baseUrl": "./",
16 | "outDir": "./dist"
17 | },
18 | "include": [
19 | "*.ts",
20 | "**/*.ts"
21 | ],
22 | "exclude": [
23 | "node_modules",
24 | "./**/*.spec.ts",
25 | "examples"
26 | ]
27 | }
--------------------------------------------------------------------------------