├── .nvmrc ├── .dockerignore ├── src ├── readme.md ├── logger.ts ├── amqp-ts.spec-i.ts ├── amqp-ts.spec.ts └── amqp-ts.ts ├── docker-bash.cmd ├── vscode-recommended ├── settings.json ├── readme.md ├── tasks.json └── launch.json ├── .vscode ├── settings.json ├── tasks.json └── launch.json ├── tools ├── restart-rabbit.sh ├── mocha │ └── setup.js ├── alive.js ├── restart-rabbit-windows.cmd └── tslint │ └── tslint-node.json ├── tutorials ├── readme.md ├── 1_hello_world │ ├── receive.js │ └── send.js ├── 3_publish_subscribe │ ├── receive_logs.js │ └── emit_log.js ├── 2_work_queues │ ├── new_task.js │ └── worker.js ├── 6_rpc │ ├── rpc_server.js │ └── rpc_client.js ├── 5_topics │ ├── emit_log_topic.js │ └── receive_logs_topic.js └── 4-routing │ ├── emit_log_direct.js │ └── receive_logs_direct.js ├── tslint.json ├── tsconfig.json ├── docker-compose.yml ├── .gitignore ├── typings.json ├── .github └── workflows │ └── build-check.yml ├── LICENSE ├── package.json ├── lib ├── logger.js └── amqp-ts.d.ts ├── Dockerfile ├── gulpfile.js └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | v16.13.2 -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /src/readme.md: -------------------------------------------------------------------------------- 1 | Typescript source code -------------------------------------------------------------------------------- /docker-bash.cmd: -------------------------------------------------------------------------------- 1 | docker exec -it dev-amqp-ts bash -------------------------------------------------------------------------------- /vscode-recommended/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "tslint.configFile": "tools/tslint/tslint-node.json" 4 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "tslint.enable": true, 4 | "typescript.check.workspaceVersion": false 5 | } -------------------------------------------------------------------------------- /tools/restart-rabbit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | container_id=`docker ps -q --filter ancestor=rabbit` 4 | docker exec $container_id rabbitmqctl stop_app 5 | docker exec $container_id rabbitmqctl start_app 6 | -------------------------------------------------------------------------------- /tutorials/readme.md: -------------------------------------------------------------------------------- 1 | RabbitMQ Tutorials 2 | ================== 3 | 4 | This contains amqp-ts version of the [RabbitMQ tutorials](https://www.rabbitmq.com/getstarted.html). 5 | 6 | The examples are provided in Javascript. -------------------------------------------------------------------------------- /tools/mocha/setup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Ab on 11-6-2015. 3 | */ 4 | 5 | // required to correctly display source mapped errors in mocha 6 | require('source-map-support').install({ 7 | handleUncaughtExceptions: false 8 | }); -------------------------------------------------------------------------------- /tools/alive.js: -------------------------------------------------------------------------------- 1 | // simple tool that keeps the development docker container from closing 2 | 3 | console.log("Starting alive at " + new Date().toLocaleString()); 4 | 5 | setInterval(function() { 6 | console.log("Still alive at " + new Date().toLocaleString()); 7 | }, 60000) -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-unused-expression": true, 4 | "no-duplicate-variable": true, 5 | "no-duplicate-key": true, 6 | "no-unused-variable": true, 7 | "curly": true, 8 | "class-name": true, 9 | "semicolon": ["always"], 10 | "triple-equals": true 11 | } 12 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES5", 4 | "module": "commonjs", 5 | "removeComments": true, 6 | "preserveConstEnums": true, 7 | "outDir": "./transpiled", 8 | "declaration": true, 9 | "rootDir": "./src", 10 | "sourceMap": true 11 | } 12 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | dev: 4 | container_name: "dev-amqp-ts" 5 | build: . 6 | ports: 7 | - "5858:5858" 8 | volumes: 9 | - ".:/src/" 10 | depends_on: 11 | - "rabbitmq" 12 | links: 13 | - "rabbitmq" 14 | rabbitmq: 15 | container_name: "rabbitmq" 16 | image: rabbitmq:management 17 | ports: 18 | - 15672:15672 -------------------------------------------------------------------------------- /vscode-recommended/readme.md: -------------------------------------------------------------------------------- 1 | Recommended Visual Studio Code settings 2 | ======================================= 3 | 4 | If you copy _launch.json_ and _tasks.json_ to the _.vscode_ folder, you get the following features: 5 | - remote build on the docker image triggered by CTRL-SHIFT-B (the default build shortcut) 6 | - remote debug task available for debugging the mocha tests on the docker image. Type CTRL-SHIFT-P 'run', select 'run tasks', then select 'debug'. You can now run the 'Attach' debugger in Visual Studio Code to start debugging. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage directory used by istanbul 11 | coverage 12 | 13 | # Transpiled code, generated by Typescript 14 | transpiled 15 | 16 | # The lib directory contains the generated library, do not exclude! 17 | 18 | # node-waf configuration 19 | .lock-wscript 20 | 21 | # Dependency directory 22 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 23 | node_modules 24 | 25 | # Dev Environments 26 | .idea 27 | # Do not exclude project settings of default develop environment 28 | # .vscode -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "amqplib": "registry:npm/amqplib#0.4.1+20160401203933", 4 | "bluebird": "registry:npm/bluebird#3.3.4+20160427051630" 5 | }, 6 | "ambientDependencies": { 7 | "amqplib": "registry:dt/amqplib#0.3.0+20160317120654", 8 | "node": "registry:dt/node#4.0.0+20160509082809", 9 | "when": "registry:dt/when#2.4.0+20160317120654", 10 | "winston": "registry:dt/winston#0.0.0+20160417152829" 11 | }, 12 | "devDependencies": { 13 | "chai": "registry:npm/chai#3.5.0+20160415060238" 14 | }, 15 | "ambientDevDependencies": { 16 | "mocha": "registry:dt/mocha#2.2.5+20160317120654" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tutorials/1_hello_world/receive.js: -------------------------------------------------------------------------------- 1 | //var amqp = require('amqp-ts'); // normal use 2 | var amqp = require('../../lib/amqp-ts'); // for use inside this package 3 | 4 | // create a new connection (async) 5 | var connection = new amqp.Connection(); 6 | 7 | // declare a new queue, it will be created if it does not already exist (async) 8 | var queue = connection.declareQueue('hello', {durable: false}); 9 | 10 | // create a consumer function for the queue 11 | // this will keep running until the program is halted or is stopped with queue.stopConsumer() 12 | queue.activateConsumer(function(message) { 13 | console.log('received message: ' + message.getContent()); 14 | }, {noAck: true}); 15 | -------------------------------------------------------------------------------- /tutorials/1_hello_world/send.js: -------------------------------------------------------------------------------- 1 | //var amqp = require('amqp-ts'); // normal use 2 | var amqp = require('../../lib/amqp-ts'); // for use inside this package 3 | 4 | // create a new connection (async) 5 | var connection = new amqp.Connection(); 6 | 7 | // declare a new queue, it will be created if it does not already exist (async) 8 | var queue = connection.declareQueue('hello', {durable: false}); 9 | 10 | // send a message, it will automatically be sent after the connection and the queue declaration 11 | // have finished successfully 12 | var message = new amqp.Message('Hello World!') 13 | queue.send(message); 14 | 15 | // not exactly true, but the message will be sent shortly 16 | console.log(' [x] Sent \'Hello World!\''); 17 | 18 | // after half a second close the connection 19 | setTimeout(function() { 20 | connection.close(); 21 | }, 500); -------------------------------------------------------------------------------- /tutorials/3_publish_subscribe/receive_logs.js: -------------------------------------------------------------------------------- 1 | //var amqp = require('amqp-ts'); // normal use 2 | var amqp = require('../../lib/amqp-ts'); // for use inside this package 3 | 4 | // create a new connection (async) 5 | var connection = new amqp.Connection(); 6 | 7 | // declare a new exchange, it will be created if it does not already exist (async) 8 | var exchange = connection.declareExchange('logs', 'fanout', {durable: false}); 9 | 10 | // declare a new queue, it will be created if it does not already exist (async) 11 | var queue = connection.declareQueue('', {exclusive: true}); 12 | 13 | // connect the queue to the exchange 14 | queue.bind(exchange); 15 | 16 | // create a consumer function for the queue 17 | // this will keep running until the program is halted or is stopped with queue.stopConsumer() 18 | queue.activateConsumer(function(message) { 19 | console.log(' [x] ' + message.getContent()); 20 | }); 21 | -------------------------------------------------------------------------------- /tutorials/2_work_queues/new_task.js: -------------------------------------------------------------------------------- 1 | //var amqp = require('amqp-ts'); // normal use 2 | var amqp = require('../../lib/amqp-ts'); // for use inside this package 3 | 4 | // create a new connection (async) 5 | var connection = new amqp.Connection(); 6 | 7 | // declare a new queue, it will be created if it does not already exist (async) 8 | var queue = connection.declareQueue('task_queue', {durable: true}); 9 | 10 | // get the message from the command line 11 | var message = new amqp.Message(process.argv.slice(2).join(' ') || 'Hello World!', {persistent: true}); 12 | 13 | // send a message, it will automatically be sent after the connection and the queue declaration 14 | // have finished successfully 15 | queue.send(message); 16 | 17 | // not exactly true, but the message will be sent shortly 18 | console.log(' [x] Sent \'' + message + '\''); 19 | 20 | // after half a second close the connection 21 | setTimeout(function() { 22 | connection.close(); 23 | }, 500); -------------------------------------------------------------------------------- /tutorials/3_publish_subscribe/emit_log.js: -------------------------------------------------------------------------------- 1 | //var amqp = require('amqp-ts'); // normal use 2 | var amqp = require('../../lib/amqp-ts'); // for use inside this package 3 | 4 | // create a new connection (async) 5 | var connection = new amqp.Connection(); 6 | 7 | // declare a new exchange, it will be created if it does not already exist (async) 8 | var exchange = connection.declareExchange('logs', 'fanout', {durable: false}); 9 | 10 | // get the message from the command line 11 | var message = new amqp.Message(process.argv.slice(2).join(' ') || 'Hello World!'); 12 | 13 | // send a message, it will automatically be sent after the connection and the queue declaration 14 | // have finished successfully 15 | exchange.send(message); 16 | 17 | // not exactly true, but the message will be sent shortly 18 | console.log(' [x] Sent \'' + message.getContent() + '\''); 19 | 20 | // after half a second close the connection 21 | setTimeout(function() { 22 | connection.close(); 23 | }, 500); 24 | -------------------------------------------------------------------------------- /tutorials/2_work_queues/worker.js: -------------------------------------------------------------------------------- 1 | //var amqp = require('amqp-ts'); // normal use 2 | var amqp = require('../../lib/amqp-ts'); // for use inside this package 3 | 4 | // create a new connection (async) 5 | var connection = new amqp.Connection(); 6 | 7 | // declare a new queue, it will be created if it does not already exist (async) 8 | var queue = connection.declareQueue('task_queue', {durable: true}); 9 | 10 | // create a consumer function for the queue 11 | // this will keep running until the program is halted or is stopped with queue.stopConsumer() 12 | queue.activateConsumer(function(message) { 13 | // fake a second of work for every dot in the message 14 | var content = message.getContent(); 15 | var seconds = content.split('.').length - 1; 16 | console.log(' [x] received message: ' + content); 17 | setTimeout(function() { 18 | console.log(" [x] Done"); 19 | message.ack(); // acknowledge that the message has been received (and processed) 20 | }, seconds * 1000); 21 | }, {noAck: false}); 22 | -------------------------------------------------------------------------------- /tutorials/6_rpc/rpc_server.js: -------------------------------------------------------------------------------- 1 | //var amqp = require('amqp-ts'); // normal use 2 | var amqp = require('../../lib/amqp-ts'); // for use inside this package 3 | 4 | // create a new connection (async) 5 | var connection = new amqp.Connection(); 6 | 7 | // declare a the rpc queue, it will be created if it does not already exist (async) 8 | var queue = connection.declareQueue('rpc_queue', {durable: false}); 9 | 10 | // create an rpc consumer function for the queue, automatically returns the return value of the 11 | // consumer function to the replyTo queue, if it exists 12 | // this will keep running until the program is halted or is stopped with queue.stopConsumer() 13 | queue.activateConsumer(function(message) { 14 | var n = parseInt(message.getContent()); 15 | console.log(' [.] fib(' + n + ')'); 16 | 17 | // return fibonacci number 18 | return fibonacci(n); 19 | }, {noAck: true}); 20 | 21 | // compute the fibonacci number 22 | function fibonacci(n) { 23 | if (n == 0 || n == 1) 24 | return n; 25 | else 26 | return fibonacci(n - 1) + fibonacci(n - 2); 27 | } 28 | -------------------------------------------------------------------------------- /vscode-recommended/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "0.1.0", 5 | "command": "docker", 6 | "isShellCommand": true, 7 | "args": [ 8 | "exec", 9 | "dev-amqp-ts" 10 | ], 11 | "showOutput": "always", 12 | "tasks": [ 13 | { 14 | "taskName": "build", 15 | "suppressTaskName": true, 16 | "isBuildCommand": true, 17 | "isWatching": false, 18 | "args": [ 19 | "gulp" 20 | ] 21 | }, 22 | { 23 | "taskName": "debug", 24 | "suppressTaskName": true, 25 | "isBuildCommand": false, 26 | "isWatching": false, 27 | "args": [ 28 | "node", 29 | "--debug-brk", 30 | "--nolazy", 31 | "/src/node_modules/mocha/bin/_mocha", 32 | "transpiled/amqp-ts.spec.js" 33 | ] 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /tutorials/6_rpc/rpc_client.js: -------------------------------------------------------------------------------- 1 | //var amqp = require('amqp-ts'); // normal use 2 | var amqp = require('../../lib/amqp-ts'); // for use inside this package 3 | 4 | // create a new connection (async) 5 | var connection = new amqp.Connection(); 6 | 7 | // declare the rpc_queue queue, it will be created if it does not already exist (async) 8 | var queue = connection.declareQueue('rpc_queue', {durable: false}); 9 | 10 | // get the number for fibonacci from the command line 11 | var args = process.argv.slice(2); 12 | var num = parseInt(args[0]); 13 | 14 | console.log(' [x] Requesting fib(%d)', num); 15 | 16 | // easy optimized rpc for RabbitMQ 17 | // send a rpc request, it will automatically be sent after the the queue declaration 18 | // has finished successfully 19 | queue.rpc(num).then(function(result) { 20 | console.log(' [.] Got ', result.getContent()); 21 | }); 22 | 23 | // or use the method explained in the tutorial 24 | // todo: write the code! 25 | 26 | 27 | // after half a second close the connection 28 | setTimeout(function() { 29 | connection.close(); 30 | }, 500); 31 | -------------------------------------------------------------------------------- /tutorials/5_topics/emit_log_topic.js: -------------------------------------------------------------------------------- 1 | //var amqp = require('amqp-ts'); // normal use 2 | var amqp = require('../../lib/amqp-ts'); // for use inside this package 3 | 4 | // create a new connection (async) 5 | var connection = new amqp.Connection(); 6 | 7 | // declare a new exchange, it will be created if it does not already exist (async) 8 | var exchange = connection.declareExchange('topic_logs', 'topic', {durable: false}); 9 | 10 | // get the topic key and message from the command line 11 | var args = process.argv.slice(2); 12 | var message = new amqp.Message(args.slice(1).join(' ') || 'Hello World!'); 13 | var key = (args.length > 0) ? args[0] : 'anonymous.info'; 14 | 15 | // send a message, it will automatically be sent after the connection and the queue declaration 16 | // have finished successfully 17 | exchange.send(message, key); 18 | 19 | // not exactly true, but the message will be sent shortly 20 | console.log(' [x] Sent ' + key + ': \'' + message.getContent() + '\''); 21 | 22 | // after half a second close the connection 23 | setTimeout(function() { 24 | connection.close(); 25 | }, 500); 26 | -------------------------------------------------------------------------------- /tutorials/4-routing/emit_log_direct.js: -------------------------------------------------------------------------------- 1 | //var amqp = require('amqp-ts'); // normal use 2 | var amqp = require('../../lib/amqp-ts'); // for use inside this package 3 | 4 | // create a new connection (async) 5 | var connection = new amqp.Connection(); 6 | 7 | // declare a new exchange, it will be created if it does not already exist (async) 8 | var exchange = connection.declareExchange('direct_logs', 'direct', {durable: false}); 9 | 10 | // get the severity and message from the command line 11 | var args = process.argv.slice(2); 12 | var message = new amqp.Message(args.slice(1).join(' ') || 'Hello World!'); 13 | var severity = (args.length > 0) ? args[0] : 'info'; 14 | 15 | // send a message, it will automatically be sent after the connection and the queue declaration 16 | // have finished successfully 17 | exchange.send(message, severity); 18 | 19 | // not exactly true, but the message will be sent shortly 20 | console.log(' [x] Sent ' + severity + ' \'' + message.getContent() + '\''); 21 | 22 | // after half a second close the connection 23 | setTimeout(function() { 24 | connection.close(); 25 | }, 500); 26 | -------------------------------------------------------------------------------- /.github/workflows/build-check.yml: -------------------------------------------------------------------------------- 1 | name: Build and Verify JS 2 | on: 3 | pull_request: 4 | 5 | jobs: 6 | build-and-verify-js: 7 | runs-on: ubuntu-latest 8 | name: Build and Verify JS 9 | steps: 10 | - uses: zendesk/checkout@v3 11 | name: Checkout 12 | 13 | - uses: zendesk/setup-node@v3 14 | name: Setup Node 15 | with: 16 | node-version-file: '.nvmrc' 17 | 18 | - uses: zendesk/cache@v3 19 | name: Cache Node development dependencies 20 | id: node-cache 21 | with: 22 | path: node_modules 23 | key: ${{ runner.os }}-node_modules-${{ env.NODE_VERSION }}-${{ hashFiles('package-lock.json') }} 24 | 25 | - name: Install Node development dependencies 26 | if: steps.node-cache.outputs.cache-hit != 'true' 27 | run: npm ci 28 | 29 | - name: Build lib 30 | run: npm run build 31 | 32 | - name: Check for uncommitted changes 33 | run: | 34 | git diff --exit-code || (echo "Uncommitted changes found after build. Please commit any changes that result from the build." && exit 1) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 abreits 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 | 23 | -------------------------------------------------------------------------------- /tutorials/5_topics/receive_logs_topic.js: -------------------------------------------------------------------------------- 1 | //var amqp = require('amqp-ts'); // normal use 2 | var amqp = require('../../lib/amqp-ts'); // for use inside this package 3 | 4 | var args = process.argv.slice(2); 5 | 6 | var severity = args[0]; 7 | if (!severity) { 8 | console.log('Usage: receive_logs_topic.js .'); 9 | process.exit(1); 10 | } 11 | 12 | // create a new connection (async) 13 | var connection = new amqp.Connection(); 14 | 15 | // declare a new exchange, it will be created if it does not already exist (async) 16 | var exchange = connection.declareExchange('topic_logs', 'topic', {durable: false}); 17 | 18 | // declare a new queue, it will be created if it does not already exist (async) 19 | var queue = connection.declareQueue('', {exclusive: true}); 20 | 21 | // connect the queue to the exchange for each key pattern 22 | args.forEach(function(key) { 23 | queue.bind(exchange, key); 24 | }); 25 | 26 | // create a consumer function for the queue 27 | // this will keep running until the program is halted or is stopped with queue.stopConsumer() 28 | queue.activateConsumer(function(message) { 29 | var content = message.content.toString(); 30 | var routingKey = message.fields.routingKey; 31 | console.log(' [x] ' + routingKey + ' : \'' + content + '\''); 32 | }, {rawMessage: true, noAck: true}); 33 | -------------------------------------------------------------------------------- /tutorials/4-routing/receive_logs_direct.js: -------------------------------------------------------------------------------- 1 | //var amqp = require('amqp-ts'); // normal use 2 | var amqp = require('../../lib/amqp-ts'); // for use inside this package 3 | 4 | var args = process.argv.slice(2); 5 | 6 | var severity = args[0]; 7 | if (!severity) { 8 | console.log('Usage: receive_logs_direct.js [info] [warning] [error]'); 9 | process.exit(1); 10 | } 11 | 12 | // create a new connection (async) 13 | var connection = new amqp.Connection(); 14 | 15 | // declare a new exchange, it will be created if it does not already exist (async) 16 | var exchange = connection.declareExchange('direct_logs', 'direct', {durable: false}); 17 | 18 | // declare a new queue, it will be created if it does not already exist (async) 19 | var queue = connection.declareQueue('', {exclusive: true}); 20 | 21 | // connect the queue to the exchange for each severity 22 | args.forEach(function(severity) { 23 | queue.bind(exchange, severity); 24 | }); 25 | 26 | // create a consumer function for the queue 27 | // this will keep running until the program is halted or is stopped with queue.stopConsumer() 28 | queue.activateConsumer(function(message) { 29 | var content = message.content.toString(); 30 | var routingKey = message.fields.routingKey; 31 | console.log(' [x] ' + routingKey + ' : \'' + content + '\''); 32 | }, {rawMessage: true, noAck: true}); 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "amqp-ts", 3 | "version": "1.7.0", 4 | "description": "Easy to use AMQP library written in Typescript (using amqplib).", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/abreits/amqp-ts" 8 | }, 9 | "keywords": [ 10 | "amqp", 11 | "rabbitmq", 12 | "typescript" 13 | ], 14 | "license": "MIT", 15 | "author": "Ab Reitsma", 16 | "main": "lib/amqp-ts.js", 17 | "typings": "lib/amqp-ts.d.ts", 18 | "scripts": { 19 | "build": "gulp build:only", 20 | "docker-develop": "npm install && node tools/alive", 21 | "test": "gulp test", 22 | "test-integration": "gulp test:integration" 23 | }, 24 | "dependencies": { 25 | "@types/node": "^12.7.2", 26 | "amqplib": "0.10.3", 27 | "bluebird": "^3.3.5", 28 | "lodash": "^4.17.21", 29 | "winston": "3.7.2" 30 | }, 31 | "devDependencies": { 32 | "@types/mocha": "^5.2.7", 33 | "chai": "^3.5.0", 34 | "del": "^2.2.0", 35 | "gulp": "^4.0.2", 36 | "gulp-add-src": "^1.0.0", 37 | "gulp-mocha": "^7.0.1", 38 | "gulp-rename": "^1.2.2", 39 | "gulp-sourcemaps": "^1.6.0", 40 | "gulp-tslint": "^8.1.4", 41 | "gulp-typescript": "^5.0.1", 42 | "line-reader": "^0.3.1", 43 | "merge2": "^0.3.7", 44 | "mocha": "^6.2.0", 45 | "source-map-support": "^0.3.3", 46 | "tslint": "^5.19.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "0.1.0", 5 | "command": "docker", 6 | "isShellCommand": true, 7 | "args": [ 8 | "exec", 9 | "dev-amqp-ts" 10 | ], 11 | "suppressTaskName": true, 12 | "showOutput": "always", 13 | "tasks": [ 14 | { 15 | "taskName": "docker build", 16 | "isBuildCommand": true, 17 | "args": [ 18 | "gulp" 19 | ] 20 | }, 21 | { 22 | "taskName": "docker test", 23 | "isTestCommand": true, 24 | "args": [ 25 | "gulp", 26 | "test" 27 | ] 28 | }, 29 | { 30 | "taskName": "docker coverage", 31 | "args": [ 32 | "gulp", 33 | "test:coverage" 34 | ] 35 | }, 36 | { 37 | "taskName": "debug", 38 | "suppressTaskName": true, 39 | "isBuildCommand": false, 40 | "isWatching": false, 41 | "args": [ 42 | "node", 43 | "--debug-brk", 44 | "--nolazy", 45 | "/src/node_modules/mocha/bin/_mocha", 46 | "transpiled/amqp-ts.spec.js" 47 | ] 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var lodash = require("lodash"); 4 | var winston = require("winston"); 5 | function normalizeError(error, level) { 6 | if (lodash.isString(error)) { 7 | return new Error(error); 8 | } 9 | if (!(error instanceof Error)) { 10 | return; 11 | } 12 | var normalizedError = {}; 13 | Object.assign(normalizedError, lodash.omit(error, ["response", "constructor", "toString", "errors"])); 14 | // Restore common fields form the original error 15 | Object.assign(normalizedError, { 16 | name: error.name, 17 | message: error.message, 18 | }); 19 | if (level === "error") { 20 | normalizedError.stack = error.stack; 21 | } 22 | return normalizedError; 23 | } 24 | var baseFormat = winston.format(function (context) { 25 | if (context.error) { 26 | var normalizedError = normalizeError(context.error, context.level); 27 | if (normalizedError) { 28 | context.error = normalizedError; 29 | } 30 | } 31 | return context; 32 | }); 33 | exports.logger = { 34 | jsonLogger: function (level) { 35 | var transports = [new winston.transports.Console({ level: level })]; 36 | var format = winston.format.combine(baseFormat(), winston.format.json()); 37 | return winston.createLogger({ 38 | transports: transports, 39 | format: format, 40 | }); 41 | }, 42 | }; 43 | 44 | //# sourceMappingURL=logger.js.map 45 | -------------------------------------------------------------------------------- /tools/restart-rabbit-windows.cmd: -------------------------------------------------------------------------------- 1 | ::::::::::::::::::::::::::::::::::::::::: 2 | :: Automatically check & get admin rights 3 | ::::::::::::::::::::::::::::::::::::::::: 4 | @echo off 5 | REM CLS 6 | ECHO. 7 | ECHO ============================= 8 | ECHO Running Admin shell 9 | ECHO ============================= 10 | 11 | :checkPrivileges 12 | NET FILE 1>NUL 2>NUL 13 | if '%errorlevel%' == '0' ( goto gotPrivileges ) else ( goto getPrivileges ) 14 | 15 | :getPrivileges 16 | if '%1'=='ELEV' (echo ELEV & shift & goto gotPrivileges) 17 | ECHO. 18 | ECHO ************************************** 19 | ECHO Invoking UAC for Privilege Escalation 20 | ECHO ************************************** 21 | 22 | setlocal DisableDelayedExpansion 23 | set "batchPath=%~0" 24 | setlocal EnableDelayedExpansion 25 | ECHO Set UAC = CreateObject^("Shell.Application"^) > "%temp%\OEgetPrivileges.vbs" 26 | ECHO args = "ELEV " >> "%temp%\OEgetPrivileges.vbs" 27 | ECHO For Each strArg in WScript.Arguments >> "%temp%\OEgetPrivileges.vbs" 28 | ECHO args = args ^& strArg ^& " " >> "%temp%\OEgetPrivileges.vbs" 29 | ECHO Next >> "%temp%\OEgetPrivileges.vbs" 30 | ECHO UAC.ShellExecute "!batchPath!", args, "", "runas", 1 >> "%temp%\OEgetPrivileges.vbs" 31 | "%SystemRoot%\System32\WScript.exe" "%temp%\OEgetPrivileges.vbs" %* 32 | exit /B 33 | 34 | :gotPrivileges 35 | if '%1'=='ELEV' shift 36 | setlocal & pushd . 37 | cd /d %~dp0 38 | 39 | :::::::::::::::::::::::::::: 40 | ::START 41 | :::::::::::::::::::::::::::: 42 | 43 | REM Run shell as admin (example) - put here code as you like 44 | REM ECHO Arguments: %1 %2 %3 %4 %5 %6 %7 %8 %9 45 | REM cmd /k 46 | net stop rabbitmq 47 | net start rabbitmq -------------------------------------------------------------------------------- /src/logger.ts: -------------------------------------------------------------------------------- 1 | import * as lodash from "lodash"; 2 | import * as winston from "winston"; 3 | 4 | function normalizeError(error: any, level: string): void | Error | object { 5 | if (lodash.isString(error)) { 6 | return new Error(error); 7 | } 8 | 9 | if (!(error instanceof Error)) { 10 | return; 11 | } 12 | 13 | const normalizedError: any = {}; 14 | Object.assign( 15 | normalizedError, 16 | lodash.omit(error, ["response", "constructor", "toString", "errors"]) 17 | ); 18 | 19 | // Restore common fields form the original error 20 | Object.assign(normalizedError, { 21 | name: error.name, 22 | message: error.message, 23 | }); 24 | 25 | if (level === "error") { 26 | normalizedError.stack = error.stack; 27 | } 28 | 29 | return normalizedError; 30 | } 31 | 32 | const baseFormat = winston.format((context) => { 33 | if (context.error) { 34 | const normalizedError = normalizeError(context.error, context.level); 35 | if (normalizedError) { 36 | context.error = normalizedError; 37 | } 38 | } 39 | 40 | return context; 41 | }); 42 | 43 | type LogFunction = (message: string, context?: { [key: string]: any }) => void; 44 | 45 | export interface LoggerInstance { 46 | silly: LogFunction; 47 | debug: LogFunction; 48 | verbose: LogFunction; 49 | info: LogFunction; 50 | warn: LogFunction; 51 | error: LogFunction; 52 | } 53 | 54 | export const logger = { 55 | jsonLogger(level: string): LoggerInstance { 56 | const transports = [new winston.transports.Console({ level })]; 57 | const format = winston.format.combine(baseFormat(), winston.format.json()); 58 | return winston.createLogger({ 59 | transports, 60 | format, 61 | }); 62 | }, 63 | }; 64 | -------------------------------------------------------------------------------- /tools/tslint/tslint-node.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "ban": [true, 4 | ["_", "extend"], 5 | ["_", "isNull"], 6 | ["_", "isDefined"] 7 | ], 8 | "class-name": true, 9 | "comment-format": [false, 10 | "check-space", 11 | "check-lowercase" 12 | ], 13 | "curly": true, 14 | "eofline": true, 15 | "forin": false, 16 | "indent": [true, 2], 17 | "interface-name": false, 18 | "jsdoc-format": true, 19 | "label-position": true, 20 | "max-line-length": [true, 140], 21 | "no-arg": true, 22 | "no-bitwise": true, 23 | "no-console": [true, 24 | "debug", 25 | "info", 26 | "time", 27 | "timeEnd", 28 | "trace" 29 | ], 30 | "no-construct": true, 31 | "no-debugger": true, 32 | "no-duplicate-variable": true, 33 | "no-empty": true, 34 | "no-eval": true, 35 | "no-string-literal": true, 36 | "no-switch-case-fall-through": true, 37 | "no-trailing-whitespace": true, 38 | "no-unused-expression": true, 39 | "no-unused-variable": false, 40 | "no-use-before-declare": false, 41 | "no-var-requires": false, 42 | "one-line": [true, 43 | "check-open-brace", 44 | "check-catch", 45 | "check-else", 46 | "check-whitespace" 47 | ], 48 | "quotemark": [true, "double"], 49 | "radix": true, 50 | "semicolon": true, 51 | "triple-equals": [true, "allow-null-check"], 52 | "typedef": [false, 53 | "callSignature", 54 | "indexSignature", 55 | "parameter", 56 | "propertySignature", 57 | "variableDeclarator" 58 | ], 59 | "typedef-whitespace": [true, 60 | ["callSignature", "noSpace"], 61 | ["catchClause", "noSpace"], 62 | ["indexSignature", "space"] 63 | ], 64 | "variable-name": false, 65 | "whitespace": [true, 66 | "check-branch", 67 | "check-decl", 68 | "check-operator", 69 | "check-separator", 70 | "check-type" 71 | ] 72 | } 73 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile to build and test this library in a docker container with an external IDE (in this case visual studio code) 2 | # 3 | # execute the following commands to build this docker image: 4 | # $ docker build -t amqp-ts . 5 | # 6 | # to start the development process with docker 7 | # $ docker run -d --hostname rabbitmq --name rabbitmq rabbitmq:3 8 | # $ 9 | # $ docker run --name dev-amqp-ts -d -v :/src --link rabbitmq amqp-ts 10 | # or to start development with docker-compose (does the hard work for you) 11 | # $ docker-compose up -d 12 | # 13 | # to build execute the following command: 14 | # $ docker exec dev-amqp-ts gulp 15 | # 16 | # to automatically build from within Visual Studio Code with CTRL-SHIFT-B 17 | # create a tasks.json file in the .vscode folder with the following content: 18 | # { 19 | # // See https://go.microsoft.com/fwlink/?LinkId=733558 20 | # // for the documentation about the tasks.json format 21 | # "version": "0.1.0", 22 | # "command": "docker", 23 | # "isShellCommand": true, 24 | # "args": ["exec", "dev-amqp-ts", "gulp"], 25 | # "showOutput": "always" 26 | # } 27 | # 28 | # to debug (in this case the mocha tests) execute the following line and run the 'Attach' Visual Studio Code Debugger: 29 | # $ docker exec dev-amqp-ts node --debug-brk --nolazy /src/node_modules/mocha/bin/_mocha transpiled/amqp-ts.spec.js 30 | # todo: create better integration 31 | FROM node:latest 32 | 33 | WORKDIR /src 34 | 35 | # install global modules needed 36 | RUN npm install -g gulp 37 | 38 | # copy and install local development libraries 39 | COPY package.json /src/ 40 | RUN npm install 41 | 42 | # needed to keep the docker version of the libraries separate from the local version 43 | VOLUME ["/src", "/src/node_modules"] 44 | # default node debug port 45 | EXPOSE 5858 46 | 47 | # define the default rabbitmq server to use 48 | ENV AMQPTEST_CONNECTION_URL=amqp://rabbitmq 49 | 50 | #initialize the docker development environment 51 | #CMD ["npm", "run", "docker-develop"] 52 | CMD ["node", "tools/alive"] -------------------------------------------------------------------------------- /vscode-recommended/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | // List of configurations. Add new configurations or edit existing ones. 4 | "configurations": [ 5 | { 6 | "request": "launch", 7 | // Name of configuration; appears in the launch configuration drop down menu. 8 | "name": "Run killTest", 9 | // Type of configuration. 10 | "type": "node", 11 | // Workspace relative or absolute path to the program. 12 | "program": "${workspaceRoot}/transpiled/killTest.js", 13 | // Automatically stop program after launch. 14 | "stopOnEntry": false, 15 | // Command line arguments passed to the program. 16 | "args": [], 17 | // Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace. 18 | "cwd": "${workspaceRoot}/", 19 | // Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH. 20 | "runtimeExecutable": null, 21 | // Optional arguments passed to the runtime executable. 22 | "runtimeArgs": [ 23 | "--nolazy" 24 | ], 25 | // Environment variables passed to the program. 26 | "env": { 27 | "NODE_ENV": "development" 28 | }, 29 | // Use JavaScript source maps (if they exist). 30 | "sourceMaps": false, 31 | // If JavaScript source maps are enabled, the generated code is expected in this directory. 32 | "outDir": null 33 | }, 34 | { 35 | "request": "launch", 36 | // Name of configuration; appears in the launch configuration drop down menu. 37 | "name": "Run mocha", 38 | // Type of configuration. Possible values: "node", "mono". 39 | "type": "node", 40 | // Workspace relative or absolute path to the program. 41 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 42 | // Automatically stop program after launch. 43 | "stopOnEntry": false, 44 | // Command line arguments passed to the program. 45 | "args": [ 46 | "transpiled/amqp-ts.spec.js" 47 | ], 48 | // Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace. 49 | "cwd": "${workspaceRoot}/", 50 | // Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH. 51 | "runtimeExecutable": null, 52 | // Environment variables passed to the program. 53 | "env": { 54 | "NODE_ENV": "production" 55 | } 56 | }, 57 | { 58 | "request": "launch", 59 | // Name of configuration; appears in the launch configuration drop down menu. 60 | "name": "Run mocha integration", 61 | // Type of configuration. Possible values: "node", "mono". 62 | "type": "node", 63 | // Workspace relative or absolute path to the program. 64 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 65 | // Automatically stop program after launch. 66 | "stopOnEntry": false, 67 | // Command line arguments passed to the program. 68 | "args": [ 69 | "transpiled/amqp-ts.spec-i.js" 70 | ], 71 | // Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace. 72 | "cwd": "${workspaceRoot}/", 73 | // Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH. 74 | "runtimeExecutable": null, 75 | // Environment variables passed to the program. 76 | "env": { 77 | "NODE_ENV": "production" 78 | } 79 | }, 80 | { 81 | "request": "attach", 82 | "name": "Attach", 83 | "type": "node", 84 | // TCP/IP address. Default is "localhost". 85 | "address": "localhost", 86 | // Port to attach to. 87 | "port": 5858, 88 | "sourceMaps": true, 89 | "remoteRoot": "/src/", 90 | "localRoot": "${workspaceRoot}/" 91 | } 92 | ] 93 | } -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Ab on 9-4-2015. 3 | */ 4 | 5 | var gulp = require('gulp'); 6 | var del = require('del'); 7 | var rename = require('gulp-rename'); 8 | var merge = require('merge2'); 9 | var addsrc = require('gulp-add-src'); 10 | var ts = require('gulp-typescript'); 11 | var tslint = require('gulp-tslint'); 12 | var sourcemaps = require('gulp-sourcemaps'); 13 | var mocha = require('gulp-mocha'); 14 | 15 | // swallow errors in watch 16 | function swallowError (error) { 17 | 18 | //If you want details of the error in the console 19 | console.log(error.toString()); 20 | 21 | this.emit('end'); 22 | } 23 | 24 | //define typescript project 25 | var tsProject = ts.createProject({ 26 | module: 'commonjs', 27 | target: 'ES5', 28 | declaration: true 29 | }); 30 | 31 | gulp.task('copy-to-lib', gulp.series(compile, function () { 32 | return gulp.src('transpiled/amqp-ts.js').pipe(gulp.dest('lib')); 33 | }, function () { 34 | return gulp.src("transpiled/amqp-ts.d.ts").pipe(gulp.dest("lib")); 35 | }, function () { 36 | return gulp.src("transpiled/logger.js").pipe(gulp.dest("lib")); 37 | }) 38 | ); 39 | 40 | 41 | gulp.task('clean:all', function () { 42 | del([ 43 | 'coverage', 44 | 'transpiled', 45 | 'node_modules' 46 | ]); 47 | }); 48 | 49 | 50 | function clean(cb) { 51 | del.sync([ 52 | 'coverage', 53 | 'transpiled' 54 | ]); 55 | cb(); 56 | } 57 | 58 | 59 | function compile() { 60 | // compile typescript 61 | var tsResult = gulp.src('src/**/*.ts') 62 | .pipe(tslint({ 63 | formatter: 'prose', 64 | configuration: 'tools/tslint/tslint-node.json' 65 | })) 66 | .pipe(tslint.report({ 67 | emitError: false 68 | })) 69 | // .pipe(addsrc.prepend('typings*/**/*.d.ts')) 70 | .pipe (sourcemaps.init()) 71 | .pipe (tsProject()); 72 | 73 | return merge([ 74 | tsResult.js 75 | .pipe(sourcemaps.write('.', { 76 | includeContent: false, 77 | sourceRoot: '../src/' 78 | })) 79 | .pipe(gulp.dest('transpiled')), 80 | tsResult.dts.pipe(gulp.dest('transpiled')) 81 | ]); 82 | } 83 | 84 | 85 | gulp.task('lint', function () { 86 | return gulp.src('src/**/*.ts') 87 | .pipe(tslint({ 88 | formatter: 'full', 89 | configuration: 'tools/tslint/tslint-node.json' 90 | })) 91 | .pipe(tslint.report()); 92 | }); 93 | 94 | 95 | // unit tests, more a fast integration test because at the moment it uses an external AMQP server 96 | gulp.task('test', gulp.series('copy-to-lib', function () { 97 | return gulp.src('transpiled/**/*.spec.js', { 98 | read: false 99 | }) 100 | .pipe(mocha({ 101 | r: 'tools/mocha/setup.js', 102 | reporter: 'spec' // 'spec', 'dot' 103 | })) 104 | .on('error', swallowError); 105 | })); 106 | 107 | // unit tests, more a fast integration test because at the moment it uses an external AMQP server 108 | gulp.task('test:dot', gulp.series('copy-to-lib', function () { 109 | return gulp.src('transpiled/**/*.spec.js', { 110 | read: false 111 | }) 112 | .pipe(mocha({ 113 | r: 'tools/mocha/setup.js', 114 | reporter: 'dot' // 'spec', 'dot' 115 | })) 116 | .on('error', swallowError); 117 | })); 118 | 119 | // integration tests, at the moment more an extended version of the unit tests 120 | gulp.task('test:integration', gulp.series('copy-to-lib', function () { 121 | return gulp.src('transpiled/**/*.spec-i.js', { 122 | read: false 123 | }) 124 | .pipe(mocha({ 125 | reporter: 'dot' // 'spec', 'dot' 126 | })) 127 | .on('error', swallowError); 128 | })); 129 | 130 | gulp.task('test:coverage', gulp.series('copy-to-lib', function () { 131 | return gulp.src('transpiled/**/*.spec.js', { 132 | read: false 133 | }) 134 | .pipe(mocha({ 135 | reporter: 'spec', // 'spec', 'dot' 136 | istanbul: true 137 | })); 138 | })); 139 | 140 | 141 | // quick fix for gulp 4, migrating from gulp 3, fixing 'task never defined' errors 142 | gulp.task('build', gulp.series(compile, 'copy-to-lib', 'test:dot')); 143 | gulp.task('build:only', gulp.series(compile, 'copy-to-lib')); 144 | gulp.task('build:clean', gulp.series(clean, compile, 'test:dot')); 145 | gulp.task('default', gulp.series('build:clean')); 146 | 147 | gulp.task('watch', gulp.series(clean, 'build'), function () { 148 | gulp.watch('server/**/*.ts', ['build']); 149 | }); 150 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | // List of configurations. Add new configurations or edit existing ones. 4 | "configurations": [ 5 | { 6 | "request": "launch", 7 | // Name of configuration; appears in the launch configuration drop down menu. 8 | "name": "Run killTest", 9 | // Type of configuration. 10 | "type": "node", 11 | // Workspace relative or absolute path to the program. 12 | "program": "${workspaceRoot}/transpiled/killTest.js", 13 | // Automatically stop program after launch. 14 | "stopOnEntry": false, 15 | // Command line arguments passed to the program. 16 | "args": [], 17 | // Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace. 18 | "cwd": "${workspaceRoot}/", 19 | // Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH. 20 | "runtimeExecutable": null, 21 | // Optional arguments passed to the runtime executable. 22 | "runtimeArgs": [ 23 | "--nolazy" 24 | ], 25 | // Environment variables passed to the program. 26 | "env": { 27 | "NODE_ENV": "development" 28 | }, 29 | // Use JavaScript source maps (if they exist). 30 | "sourceMaps": false, 31 | // If JavaScript source maps are enabled, the generated code is expected in this directory. 32 | "outDir": null 33 | }, 34 | { 35 | "request": "launch", 36 | // Name of configuration; appears in the launch configuration drop down menu. 37 | "name": "Run mocha", 38 | // Type of configuration. Possible values: "node", "mono". 39 | "type": "node", 40 | // Workspace relative or absolute path to the program. 41 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 42 | // Automatically stop program after launch. 43 | "stopOnEntry": false, 44 | // Command line arguments passed to the program. 45 | "args": [ 46 | "transpiled/amqp-ts.spec.js" 47 | ], 48 | // Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace. 49 | "cwd": "${workspaceRoot}/", 50 | // Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH. 51 | "runtimeExecutable": null, 52 | // Environment variables passed to the program. 53 | "env": { 54 | "NODE_ENV": "production" 55 | } 56 | }, 57 | { 58 | "request": "launch", 59 | // Name of configuration; appears in the launch configuration drop down menu. 60 | "name": "Run mocha integration", 61 | // Type of configuration. Possible values: "node", "mono". 62 | "type": "node", 63 | // Workspace relative or absolute path to the program. 64 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 65 | // Automatically stop program after launch. 66 | "stopOnEntry": false, 67 | // Command line arguments passed to the program. 68 | "args": [ 69 | "transpiled/amqp-ts.spec-i.js" 70 | ], 71 | // Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace. 72 | "cwd": "${workspaceRoot}/", 73 | // Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH. 74 | "runtimeExecutable": null, 75 | // Environment variables passed to the program. 76 | "env": { 77 | "NODE_ENV": "production" 78 | } 79 | }, 80 | { 81 | "request": "attach", 82 | "name": "Attach", 83 | "type": "node", 84 | // TCP/IP address. Default is "localhost". 85 | "address": "localhost", 86 | // Port to attach to. 87 | "port": 5858, 88 | "sourceMaps": true, 89 | "remoteRoot": "/src/", 90 | "localRoot": "${workspaceRoot}/" 91 | }, 92 | { 93 | "request": "attach", 94 | "name": "Docker mocha tests", 95 | "type": "node", 96 | // TCP/IP address. Default is "localhost". 97 | "address": "localhost", 98 | // Start docker program to debug 99 | "runtimeExecutable": "docker", 100 | "runtimeArgs": [ 101 | "exec", 102 | "dev-amqp-ts", 103 | "node", 104 | "--debug-brk", 105 | "--nolazy", 106 | "/src/node_modules/mocha/bin/_mocha", 107 | "transpiled/amqp-ts.spec.js" 108 | ], 109 | // Port to attach to. 110 | "port": 5858, 111 | "sourceMaps": true, 112 | "remoteRoot": "/src/", 113 | "localRoot": "${workspaceRoot}/" 114 | } 115 | ] 116 | } -------------------------------------------------------------------------------- /src/amqp-ts.spec-i.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Integration tests for AmqpSimple 3 | * Created by Ab on 2015-10-21. 4 | */ 5 | import * as Chai from "chai"; 6 | var expect = Chai.expect; 7 | 8 | import * as Amqp from "../lib/amqp-ts"; 9 | import { logger } from "../src/logger"; 10 | 11 | /** 12 | * Test using a local rabbitmq instance 13 | */ 14 | // define test defaults 15 | var ConnectionUrl = process.env.AMQPTEST_CONNECTION_URL || "amqp://localhost"; 16 | var UnitTestLongTimeout = process.env.AMQPTEST_LONG_TIMEOUT || 60000; 17 | 18 | // needed for server restart tests 19 | var os = require("os"); 20 | var isWin = /^win/.test(os.platform()); 21 | var path = require("path"); 22 | var cp = require("child_process"); 23 | export var log = logger.jsonLogger(process.env.AMQPTS_LOGLEVEL || "error"); 24 | 25 | /* istanbul ignore next */ 26 | function restartAmqpServer() { 27 | "use strict"; 28 | // windows only code 29 | console.log("shutdown and restart rabbitmq"); 30 | if (isWin) { 31 | try { 32 | cp.execSync("net stop rabbitmq"); 33 | cp.exec("net start rabbitmq"); 34 | } catch (err) { 35 | log.error( 36 | "Unable to shutdown and restart RabbitMQ, possible solution: use elevated permissions (start an admin shell)" 37 | ); 38 | throw new Error("Unable to restart rabbitmq, error:\n" + err.message); 39 | } 40 | } else { 41 | try { 42 | cp.execSync("./tools/restart-rabbit.sh"); 43 | } catch (err) { 44 | log.error("Unable to shutdown and restart RabbitMQ"); 45 | throw new Error("Unable to restart rabbitmq, error:\n" + err.message); 46 | } 47 | } 48 | } 49 | 50 | /* istanbul ignore next */ 51 | describe("AMQP Connection class automatic reconnection", function () { 52 | // cleanup function for the AMQP connection, also tests the Connection.deleteConfiguration method 53 | function cleanup(connection, done, error?) { 54 | connection 55 | .deleteConfiguration() 56 | .then(() => { 57 | return connection.close(); 58 | }) 59 | .then( 60 | () => { 61 | done(error); 62 | }, 63 | (err) => { 64 | done(err); 65 | } 66 | ); 67 | } 68 | 69 | this.timeout(UnitTestLongTimeout); // define long timeout for rabbitmq service restart 70 | it("should reconnect a queue when detecting a broken connection because of a server restart", (done) => { 71 | // initialize 72 | var connection = new Amqp.Connection(ConnectionUrl); 73 | 74 | // test code 75 | var queue = connection.declareQueue("TestQueue"); 76 | queue 77 | .activateConsumer( 78 | (message) => { 79 | try { 80 | expect(message.getContent()).equals("Test"); 81 | cleanup(connection, done); 82 | } catch (err) { 83 | cleanup(connection, done, err); 84 | } 85 | }, 86 | { noAck: true } 87 | ) 88 | .then(() => { 89 | restartAmqpServer(); 90 | setTimeout(() => { 91 | var msg = new Amqp.Message("Test"); 92 | queue.send(msg); 93 | }, 1000); 94 | }) 95 | .catch((err) => { 96 | console.log("Consumer intialization FAILED!!!"); 97 | done(err); 98 | }); 99 | }); 100 | 101 | it("should reconnect and rebuild a complete configuration when detecting a broken connection because of a server restart", (done) => { 102 | // initialize 103 | var connection = new Amqp.Connection(ConnectionUrl); 104 | 105 | // test code 106 | var exchange1 = connection.declareExchange("TestExchange1"); 107 | var exchange2 = connection.declareExchange("TestExchange2"); 108 | var queue = connection.declareQueue("TestQueue"); 109 | exchange2.bind(exchange1); 110 | queue.bind(exchange2); 111 | queue 112 | .activateConsumer( 113 | (message) => { 114 | try { 115 | expect(message.getContent()).equals("Test"); 116 | cleanup(connection, done); 117 | } catch (err) { 118 | cleanup(connection, done, err); 119 | } 120 | }, 121 | { noAck: true } 122 | ) 123 | .then(() => { 124 | restartAmqpServer(); 125 | setTimeout(() => { 126 | var msg = new Amqp.Message("Test"); 127 | exchange1.send(msg); 128 | }, 1000); 129 | }) 130 | .catch((err) => { 131 | console.log("Consumer intialization FAILED!!!"); 132 | done(err); 133 | }); 134 | }); 135 | 136 | it("should reconnect and retrieve messages waiting on the queue", (done) => { 137 | // initialize 138 | var connection = new Amqp.Connection(ConnectionUrl); 139 | 140 | // test code 141 | var queue = connection.declareQueue("TestQueue"); 142 | 143 | var msg = new Amqp.Message("Test", { 144 | persistent: true, 145 | }); 146 | msg.sendTo(queue).then(() => { 147 | restartAmqpServer(); 148 | 149 | setTimeout(() => { 150 | queue 151 | .activateConsumer( 152 | (message) => { 153 | try { 154 | expect(message.getContent()).equals("Test"); 155 | cleanup(connection, done); 156 | } catch (err) { 157 | cleanup(connection, done, err); 158 | } 159 | }, 160 | { noAck: true } 161 | ) 162 | .catch((err) => { 163 | console.log("Consumer intialization FAILED!!!"); 164 | done(err); 165 | }); 166 | }, 1000); 167 | }); 168 | }); 169 | }); 170 | -------------------------------------------------------------------------------- /lib/amqp-ts.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * AmqpSimple.ts - provides a simple interface to read from and write to RabbitMQ amqp exchanges 3 | * Created by Ab on 17-9-2015. 4 | * 5 | * methods and properties starting with '_' signify that the scope of the item should be limited to 6 | * the inside of the enclosing namespace. 7 | */ 8 | /// 9 | import * as AmqpLib from "amqplib/callback_api"; 10 | import * as Promise from "bluebird"; 11 | import { EventEmitter } from "events"; 12 | export declare var log: import("./logger").LoggerInstance; 13 | export declare class Connection extends EventEmitter { 14 | initialized: Promise; 15 | private url; 16 | private socketOptions; 17 | private reconnectStrategy; 18 | private connectedBefore; 19 | private rebuildPromise; 20 | _connection: AmqpLib.Connection; 21 | _retry: number; 22 | _rebuilding: boolean; 23 | _isClosing: boolean; 24 | isConnected: boolean; 25 | _exchanges: { 26 | [id: string]: Exchange; 27 | }; 28 | _queues: { 29 | [id: string]: Queue; 30 | }; 31 | _bindings: { 32 | [id: string]: Binding; 33 | }; 34 | constructor(url?: string, socketOptions?: any, reconnectStrategy?: Connection.ReconnectStrategy); 35 | private rebuildConnection; 36 | private tryToConnect; 37 | _rebuildAll(error: Error): Promise; 38 | close(): Promise; 39 | /** 40 | * Make sure the whole defined connection topology is configured: 41 | * return promise that fulfills after all defined exchanges, queues and bindings are initialized 42 | */ 43 | completeConfiguration(): Promise; 44 | /** 45 | * Delete the whole defined connection topology: 46 | * return promise that fulfills after all defined exchanges, queues and bindings have been removed 47 | */ 48 | deleteConfiguration(): Promise; 49 | declareExchange(name: string, type?: string, options?: Exchange.DeclarationOptions): Exchange; 50 | declareQueue(name: string, options?: Queue.DeclarationOptions): Queue; 51 | declareTopology(topology: Connection.Topology): Promise; 52 | readonly getConnection: AmqpLib.Connection; 53 | } 54 | export declare namespace Connection { 55 | interface ReconnectStrategy { 56 | retries: number; 57 | interval: number; 58 | } 59 | interface Topology { 60 | exchanges: { 61 | name: string; 62 | type?: string; 63 | options?: any; 64 | }[]; 65 | queues: { 66 | name: string; 67 | options?: any; 68 | }[]; 69 | bindings: { 70 | source: string; 71 | queue?: string; 72 | exchange?: string; 73 | pattern?: string; 74 | args?: any; 75 | }[]; 76 | } 77 | } 78 | export declare class Message { 79 | content: Buffer; 80 | fields: any; 81 | properties: any; 82 | _channel: AmqpLib.ConfirmChannel; 83 | _message: AmqpLib.Message; 84 | constructor(content?: any, options?: any); 85 | setContent(content: any): void; 86 | getContent(): any; 87 | sendTo(destination: Exchange | Queue, routingKey?: string): Promise; 88 | ack(allUpTo?: boolean): void; 89 | nack(allUpTo?: boolean, requeue?: boolean): void; 90 | reject(requeue?: boolean): void; 91 | } 92 | export declare class Exchange { 93 | initialized: Promise; 94 | _consumer_handlers: Array<[string, any]>; 95 | _isConsumerInitializedRcp: boolean; 96 | _connection: Connection; 97 | _channel: AmqpLib.ConfirmChannel; 98 | _name: string; 99 | _type: string; 100 | _options: Exchange.DeclarationOptions; 101 | _deleting: Promise; 102 | _closing: Promise; 103 | readonly name: string; 104 | readonly type: string; 105 | constructor(connection: Connection, name: string, type?: string, options?: Exchange.DeclarationOptions); 106 | _initialize(): void; 107 | waitForConfirms(): any; 108 | /** 109 | * deprecated, use 'exchange.send(message: Message, routingKey?: string)' instead 110 | */ 111 | publish(content: any, routingKey?: string, options?: any): Promise; 112 | send(message: Message, routingKey?: string): Promise; 113 | rpc(requestParameters: any, routingKey?: string, callback?: (err: any, message: Message) => void): Promise; 114 | delete(): Promise; 115 | close(): Promise; 116 | bind(source: Exchange, pattern?: string, args?: any): Promise; 117 | unbind(source: Exchange, pattern?: string, args?: any): Promise; 118 | consumerQueueName(): string; 119 | /** 120 | * deprecated, use 'exchange.activateConsumer(...)' instead 121 | */ 122 | startConsumer(onMessage: (msg: any, channel?: AmqpLib.Channel) => any, options?: Queue.StartConsumerOptions): Promise; 123 | activateConsumer(onMessage: (msg: Message) => any, options?: Queue.ActivateConsumerOptions): Promise; 124 | stopConsumer(): Promise; 125 | } 126 | export declare namespace Exchange { 127 | interface DeclarationOptions { 128 | durable?: boolean; 129 | internal?: boolean; 130 | autoDelete?: boolean; 131 | alternateExchange?: string; 132 | arguments?: any; 133 | noCreate?: boolean; 134 | } 135 | interface InitializeResult { 136 | exchange: string; 137 | } 138 | } 139 | export declare class Queue { 140 | initialized: Promise; 141 | _connection: Connection; 142 | _channel: AmqpLib.ConfirmChannel; 143 | _name: string; 144 | _options: Queue.DeclarationOptions; 145 | _consumer: (msg: any, channel?: AmqpLib.Channel) => any; 146 | _isStartConsumer: boolean; 147 | _rawConsumer: boolean; 148 | _consumerOptions: Queue.StartConsumerOptions; 149 | _consumerTag: string; 150 | _consumerInitialized: Promise; 151 | _consumerStopping: boolean; 152 | _deleting: Promise; 153 | _closing: Promise; 154 | readonly name: string; 155 | constructor(connection: Connection, name: string, options?: Queue.DeclarationOptions); 156 | _initialize(): void; 157 | static _packMessageContent(content: any, options: any): Buffer; 158 | static _unpackMessageContent(msg: AmqpLib.Message): any; 159 | /** 160 | * deprecated, use 'queue.send(message: Message)' instead 161 | */ 162 | publish(content: any, options?: any): Promise; 163 | send(message: Message): void; 164 | rpc(requestParameters: any): Promise; 165 | prefetch(count: number): void; 166 | recover(): Promise; 167 | /** 168 | * deprecated, use 'queue.activateConsumer(...)' instead 169 | */ 170 | startConsumer(onMessage: (msg: any, channel?: AmqpLib.Channel) => any, options?: Queue.StartConsumerOptions): Promise; 171 | activateConsumer(onMessage: (msg: Message) => any, options?: Queue.ActivateConsumerOptions): Promise; 172 | _initializeConsumer(): void; 173 | stopConsumer(): Promise; 174 | delete(): Promise; 175 | close(): Promise; 176 | bind(source: Exchange, pattern?: string, args?: any): Promise; 177 | unbind(source: Exchange, pattern?: string, args?: any): Promise; 178 | } 179 | export declare namespace Queue { 180 | interface DeclarationOptions { 181 | exclusive?: boolean; 182 | durable?: boolean; 183 | autoDelete?: boolean; 184 | arguments?: any; 185 | messageTtl?: number; 186 | expires?: number; 187 | deadLetterExchange?: string; 188 | maxLength?: number; 189 | prefetch?: number; 190 | noCreate?: boolean; 191 | } 192 | interface StartConsumerOptions { 193 | rawMessage?: boolean; 194 | consumerTag?: string; 195 | noLocal?: boolean; 196 | noAck?: boolean; 197 | manualAck?: boolean; 198 | exclusive?: boolean; 199 | priority?: number; 200 | arguments?: Object; 201 | } 202 | interface ActivateConsumerOptions { 203 | consumerTag?: string; 204 | noLocal?: boolean; 205 | noAck?: boolean; 206 | manualAck?: boolean; 207 | exclusive?: boolean; 208 | priority?: number; 209 | arguments?: Object; 210 | } 211 | interface StartConsumerResult { 212 | consumerTag: string; 213 | } 214 | interface InitializeResult { 215 | queue: string; 216 | messageCount: number; 217 | consumerCount: number; 218 | } 219 | interface DeleteResult { 220 | messageCount: number; 221 | } 222 | } 223 | export declare class Binding { 224 | initialized: Promise; 225 | _source: Exchange; 226 | _destination: Exchange | Queue; 227 | _pattern: string; 228 | _args: any; 229 | constructor(destination: Exchange | Queue, source: Exchange, pattern?: string, args?: any); 230 | _initialize(): void; 231 | delete(): Promise; 232 | static id(destination: Exchange | Queue, source: Exchange, pattern?: string): string; 233 | static removeBindingsContaining(connectionPoint: Exchange | Queue): Promise; 234 | } 235 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | amqp-ts (AMQP TypeScript) 2 | ========================= 3 | 4 | This is a summary. See the [amqp-ts Wiki](https://github.com/abreits/amqp-ts/wiki) for the full documentation of the library. 5 | 6 | ## Contributing 7 | 8 | Before making changes, be sure that you are using the default node version set in the `.nvmrc` file. 9 | 10 | After introducing new changes, be sure to run `npm run build` to generate the transpiled JS. 11 | 12 | ## Table of Contents 13 | 14 | - [Overview](#overview) 15 | - [What's new](#whatsnew) 16 | - [Roadmap](#roadmap) 17 | 18 | 19 | Overview 20 | -------- 21 | 22 | Amqp-ts is a library for nodejs that simplifies communication with AMQP message busses written in Typescript. It has been tested on RabbitMQ. It uses the [amqplib](http://www.squaremobius.net/amqp.node/) library by [Michael Bridgen (squaremo)](https://github.com/squaremo). 23 | 24 | ### Important Changes 25 | 26 | Starting in version 0.14 the return type of [exchange.rpc](https://github.com/abreits/amqp-ts/wiki/Exchange%20class#rpc) and [queue.rpc](https://github.com/abreits/amqp-ts/wiki/Queue%20class#rpc) changed from 'Promise < any >' to 'Promise < [Message](https://github.com/abreits/amqp-ts/wiki/Message%20class) >'. 27 | 28 | Starting in version 0.12 the [Message class](https://github.com/abreits/amqp-ts/wiki/Message%20class) has been added. It is a more elegant way to send and receive messages. 29 | It is the preferred way to deal with sending and receiving messages. 30 | 31 | 32 | ### Defining Features 33 | 34 | - [High level non opinioned library], no need to worry about channels etc. 35 | - ['Lazy' initialization](#initialization), async AMQP dependencies are resolved automatically 36 | - [Automatic reconnection](#reconnect), when the connection with the AMQP server fails, the whole connection and configuration is rebuilt automatically 37 | - Written in typescript, it is compatible with the Typescript 1.6 module type definition resolution for node.js. 38 | 39 | ### Current status 40 | 41 | The library is considered production ready. 42 | 43 | It does depend on the following npm libraries: 44 | - [amqplib](http://www.squaremobius.net/amqp.node/) 45 | - [bluebird](https://github.com/petkaantonov/bluebird) 46 | - [winston](https://github.com/winstonjs/winston) 47 | 48 | The DefinitelyTyped [tsd](http://definitelytyped.org/tsd) tool is used to manage the typescript type definitions. 49 | 50 | ### Lazy Initialization 51 | 52 | No need to nest functionality, just create a connection, declare your exchanges, queues and 53 | bindings and send and receive messages. The library takes care of any direct dependencies. 54 | 55 | If you define an exchange and a queue and bind the queue to the exchange and want to make 56 | sure that the queue is connected to the exchange before you send a message to the exchange you can call the `connection.completeConfiguration()` method and act on the promise it returns. 57 | 58 | ##### ES6/Typescript Example 59 | ```TypeScript 60 | import * as Amqp from "amqp-ts"; 61 | 62 | var connection = new Amqp.Connection("amqp://localhost"); 63 | var exchange = connection.declareExchange("ExchangeName"); 64 | var queue = connection.declareQueue("QueueName"); 65 | queue.bind(exchange); 66 | queue.activateConsumer((message) => { 67 | console.log("Message received: " + message.getContent()); 68 | }); 69 | 70 | // it is possible that the following message is not received because 71 | // it can be sent before the queue, binding or consumer exist 72 | var msg = new Amqp.Message("Test"); 73 | exchange.send(msg); 74 | 75 | connection.completeConfiguration().then(() => { 76 | // the following message will be received because 77 | // everything you defined earlier for this connection now exists 78 | var msg2 = new Amqp.Message("Test2"); 79 | exchange.send(msg2); 80 | }); 81 | ``` 82 | 83 | ##### Javascript Example 84 | ```JavaScript 85 | var amqp = require("amqp-ts"); 86 | 87 | var connection = new amqp.Connection("amqp://localhost"); 88 | var exchange = connection.declareExchange("ExchangeName"); 89 | var queue = connection.declareQueue("QueueName"); 90 | queue.bind(exchange); 91 | queue.activateConsumer((message) => { 92 | console.log("Message received: " + message.getContent()); 93 | }); 94 | 95 | // it is possible that the following message is not received because 96 | // it can be sent before the queue, binding or consumer exist 97 | var msg = new amqp.Message("Test"); 98 | exchange.send(msg); 99 | 100 | connection.completeConfiguration().then(() => { 101 | // the following message will be received because 102 | // everything you defined earlier for this connection now exists 103 | var msg2 = new amqp.Message("Test2"); 104 | exchange.send(msg2); 105 | }); 106 | ``` 107 | 108 | More examples can be found in the [tutorials directory](https://github.com/abreits/amqp-ts/tree/master/tutorials). 109 | 110 | ### Connection Status 111 | To know the status of the connection: `connection.isConnected`. Returns true if the connection exists and false, otherwise. 112 | 113 | ### Events 114 | #on('open_connection', function() {...}) 115 | It's emitted when a connection is concretized and can publish/subscribe in Rabbit Bus. 116 | 117 | #on('close_connection', function() {...}) 118 | It's emitted when a connection is closed, after calling the close method. 119 | 120 | #on('lost_connection', function() {...}) 121 | It is emitted when the connection is lost and before attempting to re-establish the connection. 122 | 123 | #on('trying_connect', function() {...}) 124 | It is emitted during the time that try re-establish the connection. 125 | 126 | #on('re_established_connection', function() {...}) 127 | It is emitted when the connection is re-established. 128 | 129 | #on('error_connection', function(err) {...}) 130 | It's emitted when a error is registered during the connection. 131 | 132 | ### Automatic Reconnection 133 | 134 | When the library detects that the connection with the AMQP server is lost, it tries to automatically reconnect to the server. 135 | 136 | 137 | What's new 138 | ---------- 139 | ### version 1.4.0 140 | - now you can return a `Promise` with `queue.activateConsumer` for RPC's. 141 | The result of the resolved `Promise` will be returned to the RPC caller. 142 | 143 | ### version 1.3.0 144 | - added `noCreate` creation option property for `Exchange` and `Queue` (expects the exchange or queue to already exist 145 | in AMQP) 146 | - improved unit tests 147 | 148 | ### version 1.2.0 149 | - added `name` property for `Exchange` and `Queue` and `type` property for `Exchange` 150 | - improved consumer cleanup for `Exchange` and `Queue` methods `close` and `delete` 151 | 152 | ### version 1.1.1 153 | - added the `prefetch` option to `DeclarationOptions` in the `amqp-ts.d.ts` file 154 | 155 | ### version 1.1.0 156 | - fixed incorrect implementation of nack, syntax is now in line with [amqplib nack](http://www.squaremobius.net/amqp.node/channel_api.html#channel_nack) 157 | 158 | ### version 1.0.1 159 | - fixed bug in automatic reconnect (exponential growth of retries hanging the application) 160 | 161 | ### version 1.0.0 162 | - updated typescript definition file management from [tsd](https://github.com/DefinitelyTyped/tsd) to [typings](https://github.com/typings/typings) 163 | - added [queue.prefetch](https://github.com/abreits/amqp-ts/wiki/Queue class#prefetch) and [queue.recover](https://github.com/abreits/amqp-ts/wiki/Queue class#recover) methods 164 | - updated to version 1.0 (finally) 165 | 166 | ### version 0.14.4 167 | - fixed error when using node.js version 0.10.x: `path` library does not have a method `parse` in 0.10.x 168 | 169 | ### version 0.14.3 170 | - improved readability of readme.md on npmjs 171 | 172 | ### version 0.14.2 173 | - multiple calls of `exchange.close`, `exchange.delete`, `queue.close` and `queue.delete` return the same promise (and are thereby executed only once) 174 | 175 | ### version 0.14.1 176 | - added extra promise rejection handling for `exchange.close`, `exchange.delete`, `queue.close` and `queue.delete` 177 | 178 | ### version 0.14.0 179 | - changed the return type of [exchange.rpc](https://github.com/abreits/amqp-ts/wiki/Exchange%20class#rpc) and [queue.rpc](https://github.com/abreits/amqp-ts/wiki/Queue%20class#rpc) from 'Promise < any >' to 'Promise < [Message](https://github.com/abreits/amqp-ts/wiki/Message%20class) >' 180 | - added the option to return a Message in [exchange.activateConsumer](https://github.com/abreits/amqp-ts/wiki/Exchange%20class#activateConsumer) and [queue.activateConsumer](https://github.com/abreits/amqp-ts/wiki/Queue%20class#activateConsumer) 181 | - updated the [amqp-ts Wiki](https://github.com/abreits/amqp-ts/wiki) API documentation 182 | 183 | ### version 0.13.0 184 | - skipped to avoid bad luck :) 185 | 186 | ### version 0.12.0 187 | - added [Message class](https://github.com/abreits/amqp-ts/wiki/Message%20class) 188 | - added [exchange.send](https://github.com/abreits/amqp-ts/wiki/Exchange%20class#send) and [queue.send](https://github.com/abreits/amqp-ts/wiki/Queue%20class#send). 189 | - deprecated [exchange.publish](https://github.com/abreits/amqp-ts/wiki/Exchange%20class#publish) and [queue.publish](https://github.com/abreits/amqp-ts/wiki/Queue%20class#publish). 190 | - added [exchange.activateConsumer](https://github.com/abreits/amqp-ts/wiki/Exchange%20class#activateConsumer) and [queue.activateConsumer](https://github.com/abreits/amqp-ts/wiki/Queue%20class#activateConsumer). 191 | - deprecated [exchange.startConsumer](https://github.com/abreits/amqp-ts/wiki/Exchange%20class#startConsumer) and [queue.startConsumer](https://github.com/abreits/amqp-ts/wiki/Queue%20class#startConsumer). 192 | - changed [connection.declareExchange](https://github.com/abreits/amqp-ts/wiki/Connection%20class#declareExchange) 193 | and [connection.declareQueue](https://github.com/abreits/amqp-ts/wiki/Connection%20class#declareQueue) 194 | to prevent duplicate declaration of the same exchange/queue 195 | - added [connection.declareTopology](https://github.com/abreits/amqp-ts/wiki/Connection%20class#declareTopology) 196 | - added support functions [getMessageContent] and [setMessageContent] 197 | - fixed bug in integration test 198 | 199 | ### version 0.11.0 200 | - revised amqp-ts logging, see [Logging](https://github.com/abreits/amqp-ts/wiki/Logging) in the wiki for more details 201 | - fixed bug in tutorials library reference 202 | 203 | ### version 0.10.4 204 | - added amqp-ts examples for the [RabbitMQ tutorials](https://www.rabbitmq.com/getstarted.html) 205 | - fixed a bug in the queue.rpc 206 | - fixed documentation errors 207 | 208 | ### version 0.10.3 209 | - Moved the documentation to the wiki,only the 'Overview', 'What's new' and 'Roadmap' stay in the readme.md for npmjs and GitHub. 210 | - Improved the documentation 211 | 212 | ### version 0.10.2 213 | 214 | - rearranged this readme 215 | - added rpc support to the [Queue](#queue_rpc) and [Exchange](#exchange_rpc) for [RabbitMQ 'direct reply-to'](https://www.rabbitmq.com/direct-reply-to.html) RPC functionality 216 | - updated dependencies 217 | - updated the documentation 218 | 219 | 220 | ### version 0.10.1 221 | 222 | - added a 'low level' queue [consumer](#queue_startConsumer) that receives the raw message and can 'ack' or 'nack' these messages itself 223 | - cleanup integration tests 224 | - readme update and fixes 225 | 226 | ### version 0.10 227 | 228 | - added close methods to [Exchange](#api) and [Queue](#api) 229 | - changed Promise type for [Exchange.initialized](#exchange_initialized) and [Queue.initialized](#queue_initialized) 230 | - minor readme fixes 231 | - improved robustness for unit tests 232 | 233 | ### version 0.9.4 & 0.9.5 234 | 235 | - small code cleanup: defined optional parameter default values in typescript 236 | - fixed a few bugs when publishing a message to an exchange after a disconnect/reconnect 237 | 238 | ### version 0.9.3 239 | 240 | - Added this section 241 | - Added the roadmap section 242 | - Improved the winston logging messages 243 | 244 | 245 | 246 | Roadmap 247 | ------- 248 | 249 | The roadmap section describes things that I want to add or change in the (hopefully near) future. 250 | 251 | - Better source code documentation, maybe even use jsdoc or tsdoc to generate the api documentation 252 | -------------------------------------------------------------------------------- /src/amqp-ts.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests for amqp-ts 3 | * Created by Ab on 2015-09-16. 4 | */ 5 | import * as Promise from "bluebird"; 6 | import * as Chai from "chai"; 7 | var expect = Chai.expect; 8 | 9 | import * as AmqpLib from "amqplib"; 10 | import * as Amqp from "../lib/amqp-ts"; 11 | 12 | /** 13 | * Until we get a good mock for amqplib we will test using a local rabbitmq instance 14 | */ 15 | // define test defaults 16 | var ConnectionUrl = process.env.AMQPTEST_CONNECTION_URL || "amqp://localhost"; 17 | var UnitTestTimeout = process.env.AMQPTEST_TIMEOUT || 1500; 18 | var testExchangeNamePrefix = process.env.AMQPTEST_EXCHANGE_PREFIX || "TestExchange_"; 19 | var testQueueNamePrefix = process.env.AMQPTEST_QUEUE_PREFIX || "TestQueue_"; 20 | 21 | /* istanbul ignore next */ 22 | describe("Test amqp-ts module", function () { 23 | this.timeout(UnitTestTimeout); // define default timeout 24 | 25 | // create unique queues and exchanges for each test so they do not interfere with each other 26 | var testExchangeNumber = 0; 27 | function nextExchangeName(): string { 28 | testExchangeNumber++; 29 | return testExchangeNamePrefix + testExchangeNumber; 30 | } 31 | var testQueueNumber = 0; 32 | function nextQueueName(): string { 33 | testQueueNumber++; 34 | return testQueueNamePrefix + testQueueNumber; 35 | } 36 | 37 | // keep track of the created connections for cleanup 38 | var connections: Amqp.Connection[] = []; 39 | function getAmqpConnection() { 40 | var conn = new Amqp.Connection(ConnectionUrl, {}, { retries: 5, interval: 1500 }); 41 | connections.push(conn); 42 | return conn; 43 | } 44 | 45 | // cleanup failed tests 46 | // unfortunately does still not execute after encountering an error in mocha, perhaps in future versions 47 | function after(done) { 48 | var processAll: Promise[] = []; 49 | console.log("cleanup phase!"); 50 | for (var i = 0, l = connections.length; i < l; i++) { 51 | processAll.push(connections[i].deleteConfiguration()); 52 | } 53 | Promise.all(processAll).then(() => { 54 | done(); 55 | }).catch((err) => { 56 | done(err); 57 | }); 58 | } 59 | 60 | // cleanup function for the AMQP connection, also tests the Connection.deleteConfiguration method 61 | function cleanup(connection, done, error?) { 62 | connection.deleteConfiguration().then(() => { 63 | return connection.close(); 64 | }).then(() => { 65 | done(error); 66 | }, (err) => { 67 | done(err); 68 | }); 69 | } 70 | 71 | describe("AMQP Connection class initialization", function () { 72 | it("should create a RabbitMQ connection", function (done) { 73 | // test code 74 | var connection = getAmqpConnection(); 75 | // check result 76 | connection.initialized 77 | .then(() => { // successfully create the AMQP connection 78 | connection.close().then(() => { // successfully close the AMQP connection 79 | done(); 80 | }); 81 | }) 82 | .catch(() => { // failed to create the AMQP connection 83 | done(new Error("Failed to create a new AMQP Connection.")); 84 | }); 85 | }); 86 | }); 87 | 88 | describe("AMQP Deprecated usage tests", function () { 89 | it("should create a Queue and send and receive simple string messages", function (done) { 90 | // initialize 91 | var connection = getAmqpConnection(); 92 | 93 | // test code 94 | var queue = connection.declareQueue(nextQueueName()); 95 | 96 | queue.startConsumer((message) => { 97 | try { 98 | expect(message).equals("Test"); 99 | cleanup(connection, done); 100 | } catch (err) { 101 | cleanup(connection, done, err); 102 | } 103 | }); 104 | 105 | connection.completeConfiguration().then(() => { 106 | queue.publish("Test"); 107 | }, (err) => { // failed to configure the defined topology 108 | done(err); 109 | }); 110 | }); 111 | 112 | 113 | it("should create a Queue and send and receive simple string objects", (done) => { 114 | // initialize 115 | var connection = getAmqpConnection(); 116 | 117 | // test code 118 | var queue = connection.declareQueue(nextQueueName()); 119 | var testObj = { 120 | text: "Test" 121 | }; 122 | 123 | queue.startConsumer((message) => { 124 | try { 125 | expect(message).eql(testObj); 126 | cleanup(connection, done); 127 | } catch (err) { 128 | cleanup(connection, done, err); 129 | } 130 | }); 131 | 132 | connection.completeConfiguration().then(() => { 133 | queue.publish(testObj); 134 | }, (err) => { // failed to configure the defined topology 135 | done(err); 136 | }); 137 | }); 138 | 139 | 140 | it("should create a Queue, send a simple string message and receive the raw message", (done) => { 141 | // initialize 142 | var connection = getAmqpConnection(); 143 | 144 | // test code 145 | var queue = connection.declareQueue(nextQueueName()); 146 | var rawConsumer = (message: AmqpLib.Message, channel: AmqpLib.Channel) => { 147 | try { 148 | expect(message.content.toString()).equals("Test"); 149 | channel.ack(message); 150 | cleanup(connection, done); 151 | } catch (err) { 152 | cleanup(connection, done, err); 153 | } 154 | }; 155 | 156 | queue.startConsumer(rawConsumer, { rawMessage: true }); 157 | 158 | connection.completeConfiguration().then(() => { 159 | queue.publish("Test"); 160 | }, (err) => { // failed to configure the defined topology 161 | done(err); 162 | }); 163 | }); 164 | 165 | it("should create a Queue, send a simple string message and receive the raw message", (done) => { 166 | // initialize 167 | var connection = getAmqpConnection(); 168 | 169 | // test code 170 | var queue = connection.declareQueue(nextQueueName()); 171 | var rawConsumer = (message: AmqpLib.Message, channel: AmqpLib.Channel) => { 172 | try { 173 | expect(message.content.toString()).equals("Test"); 174 | channel.ack(message); 175 | cleanup(connection, done); 176 | } catch (err) { 177 | cleanup(connection, done, err); 178 | } 179 | }; 180 | 181 | queue.startConsumer(rawConsumer, { rawMessage: true }); 182 | 183 | connection.completeConfiguration().then(() => { 184 | queue.publish("Test"); 185 | }, (err) => { // failed to configure the defined topology 186 | done(err); 187 | }); 188 | }); 189 | 190 | it("should reconnect when sending a message to an Exchange after a broken connection", (done) => { 191 | // initialize 192 | var connection = getAmqpConnection(); 193 | 194 | // test code 195 | var exchange1 = connection.declareExchange(nextExchangeName()); 196 | var exchange2 = connection.declareExchange(nextExchangeName()); 197 | exchange2.bind(exchange1); 198 | var queue = connection.declareQueue(nextQueueName()); 199 | queue.bind(exchange1); 200 | queue.startConsumer((message) => { 201 | try { 202 | expect(message).equals("Test"); 203 | cleanup(connection, done); 204 | } catch (err) { 205 | cleanup(connection, done, err); 206 | } 207 | }).catch((err) => { 208 | console.log("Consumer intialization FAILED!!!"); 209 | done(err); 210 | }); 211 | 212 | connection.completeConfiguration().then(() => { 213 | // break connection 214 | (connection)._connection.close((err) => { 215 | if (err) { 216 | done(err); 217 | } else { 218 | // it should auto reconnect and send the message 219 | queue.publish("Test"); 220 | } 221 | }); 222 | }, (err) => { // failed to configure the defined topology 223 | done(err); 224 | }); 225 | }); 226 | 227 | it("should reconnect when sending a message to a Queue after a broken connection", (done) => { 228 | // initialize 229 | var connection = getAmqpConnection(); 230 | 231 | // test code 232 | // var exchange1 = connection.declareExchange(nextExchangeName()); 233 | // var exchange2 = connection.declareExchange(nextExchangeName()); 234 | // exchange2.bind(exchange1); 235 | var queue = connection.declareQueue(nextQueueName()); 236 | //queue.bind(exchange1); 237 | queue.startConsumer((message) => { 238 | try { 239 | expect(message).equals("Test"); 240 | cleanup(connection, done); 241 | } catch (err) { 242 | cleanup(connection, done, err); 243 | } 244 | }).catch((err) => { 245 | console.log("Consumer intialization FAILED!!!"); 246 | done(err); 247 | }); 248 | 249 | connection.completeConfiguration().then(() => { 250 | // break connection 251 | (connection)._connection.close((err) => { 252 | if (err) { 253 | done(err); 254 | } else { 255 | // it should auto reconnect and send the message 256 | queue.publish("Test"); 257 | } 258 | }); 259 | }, (err) => { // failed to configure the defined topology 260 | done(err); 261 | }); 262 | }); 263 | 264 | it("should not start 2 consumers for the same queue", (done) => { 265 | // initialize 266 | var connection = getAmqpConnection(); 267 | 268 | // test code 269 | var exchange1 = connection.declareExchange(nextExchangeName()); 270 | var queue = connection.declareQueue(nextQueueName()); 271 | 272 | queue.bind(exchange1); 273 | queue.startConsumer((message) => { 274 | cleanup(connection, done, new Error("Received unexpected message")); 275 | }); 276 | queue.startConsumer((message) => { 277 | cleanup(connection, done, new Error("Received unexpected message")); 278 | }).catch((err) => { 279 | expect(err.message).equal("amqp-ts Queue.startConsumer error: consumer already defined"); 280 | cleanup(connection, done); 281 | }); 282 | }); 283 | 284 | it("should not start 2 consumers for the same exchange", (done) => { 285 | // initialize 286 | var connection = getAmqpConnection(); 287 | 288 | // test code 289 | var exchange1 = connection.declareExchange(nextExchangeName()); 290 | 291 | exchange1.startConsumer((message) => { 292 | cleanup(connection, done, new Error("Received unexpected message")); 293 | }); 294 | exchange1.startConsumer((message) => { 295 | cleanup(connection, done, new Error("Received unexpected message")); 296 | }).catch((err) => { 297 | expect(err.message).equal("amqp-ts Exchange.startConsumer error: consumer already defined"); 298 | cleanup(connection, done); 299 | }); 300 | }); 301 | 302 | it("should stop an Exchange consumer", (done) => { 303 | // initialize 304 | var connection = getAmqpConnection(); 305 | 306 | // test code 307 | var exchange1 = connection.declareExchange(nextExchangeName()); 308 | 309 | exchange1.startConsumer((message) => { 310 | cleanup(connection, done, new Error("Received unexpected message")); 311 | }); 312 | exchange1.stopConsumer().then(() => { 313 | cleanup(connection, done); 314 | }); 315 | }); 316 | 317 | it("should not generate an error when stopping a non existing Exchange consumer", (done) => { 318 | // initialize 319 | var connection = getAmqpConnection(); 320 | 321 | // test code 322 | var exchange1 = connection.declareExchange(nextExchangeName()); 323 | 324 | exchange1.startConsumer((message) => { 325 | cleanup(connection, done, new Error("Received unexpected message")); 326 | }); 327 | exchange1.stopConsumer().then(() => { 328 | return exchange1.stopConsumer(); 329 | }).then(() => { 330 | cleanup(connection, done); 331 | }).catch((err) => { 332 | cleanup(connection, done, err); 333 | }); 334 | }); 335 | 336 | it("should not generate an error when stopping a non existing Queue consumer", (done) => { 337 | // initialize 338 | var connection = getAmqpConnection(); 339 | 340 | // test code 341 | var queue = connection.declareQueue(nextQueueName()); 342 | 343 | queue.startConsumer((message) => { 344 | cleanup(connection, done, new Error("Received unexpected message")); 345 | }); 346 | 347 | queue.stopConsumer().then(() => { 348 | return queue.stopConsumer(); 349 | }).then(() => { 350 | cleanup(connection, done); 351 | }).catch((err) => { 352 | cleanup(connection, done, err); 353 | }); 354 | }); 355 | }); 356 | 357 | describe("AMQP usage", function () { 358 | /** 359 | * normal practice is to test each feature isolated. 360 | * This is however not very practical in this situation, because we would have to test the same features over and over 361 | * We will however try to identify test failures as specific as possible 362 | */ 363 | 364 | it("should create a Queue with specified name", function (done) { 365 | // initialize 366 | var connection = getAmqpConnection(); 367 | 368 | // test code 369 | var queueName = nextQueueName(); 370 | var queue = connection.declareQueue(queueName); 371 | 372 | connection.completeConfiguration().then(() => { 373 | try { 374 | expect(queue.name).equals(queueName); 375 | cleanup(connection, done); 376 | } catch (err) { 377 | cleanup(connection, done, err); 378 | } 379 | }, (err) => { // failed to configure the defined topology 380 | done(err); 381 | }); 382 | }); 383 | 384 | it("should create an Exchange with specified name and type", function (done) { 385 | // initialize 386 | var connection = getAmqpConnection(); 387 | 388 | // test code 389 | var exchangeName = nextExchangeName(); 390 | var exchange = connection.declareExchange(exchangeName, "fanout"); 391 | 392 | connection.completeConfiguration().then(() => { 393 | try { 394 | expect(exchange.name).equals(exchangeName); 395 | expect(exchange.type).equals("fanout"); 396 | cleanup(connection, done); 397 | } catch (err) { 398 | cleanup(connection, done, err); 399 | } 400 | }, (err) => { // failed to configure the defined topology 401 | done(err); 402 | }); 403 | }); 404 | 405 | it("should create a Queue and send and receive a simple text Message", function (done) { 406 | // initialize 407 | var connection = getAmqpConnection(); 408 | 409 | // test code 410 | var queue = connection.declareQueue(nextQueueName()); 411 | 412 | queue.activateConsumer((message) => { 413 | try { 414 | expect(message.getContent()).equals("Test"); 415 | cleanup(connection, done); 416 | } catch (err) { 417 | cleanup(connection, done, err); 418 | } 419 | }, { noAck: true }); 420 | 421 | connection.completeConfiguration().then(() => { 422 | var msg = new Amqp.Message("Test"); 423 | queue.send(msg); 424 | }, (err) => { // failed to configure the defined topology 425 | done(err); 426 | }); 427 | }); 428 | 429 | it("should create a Queue and send and receive a simple text Message with ack", function (done) { 430 | // initialize 431 | var connection = getAmqpConnection(); 432 | 433 | // test code 434 | var queue = connection.declareQueue(nextQueueName()); 435 | 436 | queue.activateConsumer((message) => { 437 | try { 438 | expect(message.getContent()).equals("Test"); 439 | message.ack(); 440 | cleanup(connection, done); 441 | } catch (err) { 442 | cleanup(connection, done, err); 443 | } 444 | }); 445 | 446 | connection.completeConfiguration().then(() => { 447 | var msg = new Amqp.Message("Test"); 448 | queue.send(msg); 449 | }, (err) => { // failed to configure the defined topology 450 | done(err); 451 | }); 452 | }); 453 | 454 | it("should create a Queue and send and receive a simple text Message with nack", function (done) { 455 | // initialize 456 | var connection = getAmqpConnection(); 457 | 458 | // test code 459 | var queue = connection.declareQueue(nextQueueName()); 460 | var nacked = false; 461 | 462 | queue.activateConsumer((message) => { 463 | try { 464 | expect(message.getContent()).equals("Test"); 465 | if (nacked) { 466 | message.ack(); 467 | cleanup(connection, done); 468 | } else { 469 | message.nack(); 470 | nacked = true; 471 | } 472 | } catch (err) { 473 | cleanup(connection, done, err); 474 | } 475 | }); 476 | 477 | connection.completeConfiguration().then(() => { 478 | var msg = new Amqp.Message("Test"); 479 | queue.send(msg); 480 | }, (err) => { // failed to configure the defined topology 481 | done(err); 482 | }); 483 | }); 484 | 485 | it("should create not resend a nack(false) message", function (done) { 486 | // initialize 487 | var connection = getAmqpConnection(); 488 | 489 | // test code 490 | var queue = connection.declareQueue(nextQueueName()); 491 | var nacked = false; 492 | 493 | queue.activateConsumer((message) => { 494 | try { 495 | if (nacked) { 496 | expect(message.getContent()).equals("Test Finished"); 497 | message.ack(); 498 | cleanup(connection, done); 499 | } else { 500 | expect(message.getContent()).equals("Test"); 501 | message.nack(false, false); 502 | nacked = true; 503 | var msg = new Amqp.Message("Test Finished"); 504 | queue.send(msg); 505 | } 506 | } catch (err) { 507 | cleanup(connection, done, err); 508 | } 509 | }); 510 | 511 | connection.completeConfiguration().then(() => { 512 | var msg = new Amqp.Message("Test"); 513 | queue.send(msg); 514 | }, (err) => { // failed to configure the defined topology 515 | done(err); 516 | }); 517 | }); 518 | 519 | it("should create a Queue and send and receive a simple text Message with reject", function (done) { 520 | // initialize 521 | var connection = getAmqpConnection(); 522 | 523 | // test code 524 | var queue = connection.declareQueue(nextQueueName()); 525 | 526 | queue.activateConsumer((message) => { 527 | try { 528 | expect(message.getContent()).equals("Test"); 529 | message.reject(false); 530 | cleanup(connection, done); 531 | } catch (err) { 532 | cleanup(connection, done, err); 533 | } 534 | }); 535 | 536 | connection.completeConfiguration().then(() => { 537 | var msg = new Amqp.Message("Test"); 538 | queue.send(msg); 539 | }, (err) => { // failed to configure the defined topology 540 | done(err); 541 | }); 542 | }); 543 | 544 | it("should create a Queue and send and receive a Message with a structure", (done) => { 545 | // initialize 546 | var connection = getAmqpConnection(); 547 | 548 | // test code 549 | var queue = connection.declareQueue(nextQueueName()); 550 | var testObj = { 551 | text: "Test" 552 | }; 553 | 554 | queue.activateConsumer((message) => { 555 | try { 556 | expect(message.getContent()).eql(testObj); 557 | cleanup(connection, done); 558 | } catch (err) { 559 | cleanup(connection, done, err); 560 | } 561 | }, { noAck: true }); 562 | 563 | connection.completeConfiguration().then(() => { 564 | var msg = new Amqp.Message(testObj); 565 | queue.send(msg); 566 | }, (err) => { // failed to configure the defined topology 567 | done(err); 568 | }); 569 | }); 570 | 571 | 572 | it("should return the same Queue instance after calling connection.declareQueue multiple times", (done) => { 573 | // initialize 574 | var connection = getAmqpConnection(); 575 | 576 | // test code 577 | var queueName = nextQueueName(); 578 | var queue1 = connection.declareQueue(queueName); 579 | var queue2 = connection.declareQueue(queueName); 580 | 581 | expect(queue1).equal(queue2); 582 | 583 | connection.completeConfiguration().then(() => { 584 | cleanup(connection, done); 585 | }, (err) => { // failed to configure the defined topology 586 | done(err); 587 | }); 588 | }); 589 | 590 | it("should return the same Exchange instance after calling connection.declareExchange multiple times", (done) => { 591 | // initialize 592 | var connection = getAmqpConnection(); 593 | 594 | // test code 595 | var exchangeName = nextExchangeName(); 596 | var exchange1 = connection.declareQueue(exchangeName); 597 | var exchange2 = connection.declareQueue(exchangeName); 598 | 599 | expect(exchange2).equal(exchange2); 600 | 601 | connection.completeConfiguration().then(() => { 602 | cleanup(connection, done); 603 | }, (err) => { // failed to configure the defined topology 604 | done(err); 605 | }); 606 | }); 607 | 608 | it("should create an Exchange, Queue and binding and send and receive a simple string Message", (done) => { 609 | // initialize 610 | var connection = getAmqpConnection(); 611 | 612 | // test code 613 | var exchange = connection.declareExchange(nextExchangeName()); 614 | var queue = connection.declareQueue(nextQueueName()); 615 | queue.bind(exchange); 616 | queue.activateConsumer((message) => { 617 | try { 618 | expect(message.getContent()).equals("Test"); 619 | cleanup(connection, done); 620 | } catch (err) { 621 | cleanup(connection, done, err); 622 | } 623 | }, { noAck: true }); 624 | 625 | connection.completeConfiguration().then(() => { 626 | var msg = new Amqp.Message("Test"); 627 | exchange.send(msg); 628 | }, (err) => { // failed to configure the defined topology 629 | done(err); 630 | }); 631 | }); 632 | 633 | it("should create an Exchange, Queue and binding and send and receive a Message with structures", (done) => { 634 | // initialize 635 | var connection = getAmqpConnection(); 636 | 637 | // test code 638 | var exchange = connection.declareExchange(nextExchangeName()); 639 | var queue = connection.declareQueue(nextQueueName()); 640 | var testObj = { 641 | text: "Test" 642 | }; 643 | 644 | queue.bind(exchange); 645 | queue.activateConsumer((message) => { 646 | try { 647 | expect(message.getContent()).eql(testObj); 648 | cleanup(connection, done); 649 | } catch (err) { 650 | cleanup(connection, done, err); 651 | } 652 | }, { noAck: true }); 653 | 654 | connection.completeConfiguration().then(() => { 655 | var msg = new Amqp.Message(testObj); 656 | exchange.send(msg); 657 | }, (err) => { // failed to configure the defined topology 658 | done(err); 659 | }); 660 | }); 661 | 662 | it("should create an Exchange and send and receive a simple string Message", (done) => { 663 | // initialize 664 | var connection = getAmqpConnection(); 665 | 666 | // test code 667 | var exchange = connection.declareExchange(nextExchangeName()); 668 | exchange.activateConsumer((message) => { 669 | try { 670 | expect(message.getContent()).equals("Test"); 671 | cleanup(connection, done); 672 | } catch (err) { 673 | cleanup(connection, done, err); 674 | } 675 | }, { noAck: true }); 676 | 677 | connection.completeConfiguration().then(() => { 678 | var msg = new Amqp.Message("Test"); 679 | exchange.send(msg); 680 | }, (err) => { // failed to configure the defined topology 681 | done(err); 682 | }); 683 | }); 684 | 685 | it("should bind Exchanges", (done) => { 686 | // initialize 687 | var connection = getAmqpConnection(); 688 | 689 | // test code 690 | var exchange1 = connection.declareExchange(nextExchangeName()); 691 | var exchange2 = connection.declareExchange(nextExchangeName()); 692 | var queue = connection.declareQueue(nextQueueName()); 693 | 694 | exchange2.bind(exchange1); 695 | queue.bind(exchange2); 696 | queue.activateConsumer((message) => { 697 | try { 698 | expect(message.getContent()).equals("Test"); 699 | cleanup(connection, done); 700 | } catch (err) { 701 | cleanup(connection, done, err); 702 | } 703 | }, { noAck: true }); 704 | 705 | connection.completeConfiguration().then(() => { 706 | var msg = new Amqp.Message("Test"); 707 | exchange1.send(msg); 708 | }, (err) => { // failed to configure the defined topology 709 | done(err); 710 | }); 711 | }); 712 | 713 | it("should reconnect when sending a Message to an Exchange after a broken connection", (done) => { 714 | // initialize 715 | var connection = getAmqpConnection(); 716 | 717 | // test code 718 | var exchange1 = connection.declareExchange(nextExchangeName()); 719 | var exchange2 = connection.declareExchange(nextExchangeName()); 720 | exchange2.bind(exchange1); 721 | var queue = connection.declareQueue(nextQueueName()); 722 | queue.bind(exchange2); 723 | queue.activateConsumer((message) => { 724 | try { 725 | expect(message.getContent()).equals("Test"); 726 | cleanup(connection, done); 727 | } catch (err) { 728 | cleanup(connection, done, err); 729 | } 730 | }, { noAck: true }).catch((err) => { 731 | console.log("Consumer intialization FAILED!!!"); 732 | done(err); 733 | }); 734 | 735 | connection.completeConfiguration().then(() => { 736 | // break connection 737 | (connection)._connection.close((err) => { 738 | if (err) { 739 | done(err); 740 | } else { 741 | // it should auto reconnect and send the message 742 | var msg = new Amqp.Message("Test"); 743 | exchange1.send(msg); 744 | } 745 | }); 746 | }, (err) => { // failed to configure the defined topology 747 | done(err); 748 | }); 749 | }); 750 | 751 | it("should reconnect when sending a message to a Queue after a broken connection", (done) => { 752 | // initialize 753 | var connection = getAmqpConnection(); 754 | 755 | // test code 756 | var queue = connection.declareQueue(nextQueueName()); 757 | queue.activateConsumer((message) => { 758 | try { 759 | expect(message.getContent()).equals("Test"); 760 | cleanup(connection, done); 761 | } catch (err) { 762 | cleanup(connection, done, err); 763 | } 764 | }, { noAck: true }).catch((err) => { 765 | console.log("Consumer intialization FAILED!!!"); 766 | done(err); 767 | }); 768 | 769 | connection.completeConfiguration().then(() => { 770 | // break connection 771 | (connection)._connection.close((err) => { 772 | if (err) { 773 | done(err); 774 | } else { 775 | // it should auto reconnect and send the message 776 | var msg = new Amqp.Message("Test"); 777 | queue.send(msg); 778 | } 779 | }); 780 | }, (err) => { // failed to configure the defined topology 781 | done(err); 782 | }); 783 | }); 784 | 785 | it("should unbind Exchanges and Queues", (done) => { 786 | // initialize 787 | var connection = getAmqpConnection(); 788 | 789 | // test code 790 | var exchange1 = connection.declareExchange(nextExchangeName()); 791 | var exchange2 = connection.declareExchange(nextExchangeName()); 792 | var queue = connection.declareQueue(nextQueueName()); 793 | 794 | exchange2.bind(exchange1); 795 | queue.bind(exchange2); 796 | queue.activateConsumer((message) => { 797 | try { 798 | expect(message.getContent()).equals("Test"); 799 | exchange2.unbind(exchange1).then(() => { 800 | return queue.unbind(exchange2); 801 | }).then(() => { 802 | cleanup(connection, done); 803 | }); 804 | } catch (err) { 805 | cleanup(connection, done, err); 806 | } 807 | }, { noAck: true }); 808 | 809 | connection.completeConfiguration().then(() => { 810 | var msg = new Amqp.Message("Test"); 811 | queue.send(msg); 812 | }, (err) => { // failed to configure the defined topology 813 | done(err); 814 | }); 815 | }); 816 | 817 | it("should delete Exchanges and Queues", (done) => { 818 | // initialize 819 | var connection = getAmqpConnection(); 820 | 821 | // test code 822 | var exchange1 = connection.declareExchange(nextExchangeName()); 823 | var exchange2 = connection.declareExchange(nextExchangeName()); 824 | var queue = connection.declareQueue(nextQueueName()); 825 | 826 | exchange2.bind(exchange1); 827 | queue.bind(exchange2); 828 | queue.activateConsumer((message) => { 829 | try { 830 | expect(message.getContent()).equals("Test"); 831 | exchange2.delete().then(() => { 832 | return queue.delete(); 833 | }).then(() => { 834 | cleanup(connection, done); 835 | }); 836 | } catch (err) { 837 | cleanup(connection, done, err); 838 | } 839 | }, { noAck: true }); 840 | 841 | connection.completeConfiguration().then(() => { 842 | var msg = new Amqp.Message("Test"); 843 | queue.send(msg); 844 | }, (err) => { // failed to configure the defined topology 845 | done(err); 846 | }); 847 | }); 848 | 849 | it("should not start 2 consumers for the same queue", (done) => { 850 | // initialize 851 | var connection = getAmqpConnection(); 852 | 853 | // test code 854 | var exchange1 = connection.declareExchange(nextExchangeName()); 855 | var queue = connection.declareQueue(nextQueueName()); 856 | 857 | queue.bind(exchange1); 858 | queue.activateConsumer((message) => { 859 | cleanup(connection, done, new Error("Received unexpected message")); 860 | }); 861 | queue.activateConsumer((message) => { 862 | cleanup(connection, done, new Error("Received unexpected message")); 863 | }, { noAck: true }).catch((err) => { 864 | expect(err.message).equal("amqp-ts Queue.activateConsumer error: consumer already defined"); 865 | cleanup(connection, done); 866 | }); 867 | }); 868 | 869 | it("should not start 2 consumers for the same exchange", (done) => { 870 | // initialize 871 | var connection = getAmqpConnection(); 872 | 873 | // test code 874 | var exchange1 = connection.declareExchange(nextExchangeName()); 875 | 876 | exchange1.activateConsumer((message) => { 877 | cleanup(connection, done, new Error("Received unexpected message")); 878 | }); 879 | exchange1.activateConsumer((message) => { 880 | cleanup(connection, done, new Error("Received unexpected message")); 881 | }, { noAck: true }).catch((err) => { 882 | expect(err.message).equal("amqp-ts Exchange.activateConsumer error: consumer already defined"); 883 | cleanup(connection, done); 884 | }); 885 | }); 886 | 887 | it("should stop an Exchange consumer", (done) => { 888 | // initialize 889 | var connection = getAmqpConnection(); 890 | 891 | // test code 892 | var exchange1 = connection.declareExchange(nextExchangeName()); 893 | 894 | exchange1.activateConsumer((message) => { 895 | cleanup(connection, done, new Error("Received unexpected message")); 896 | }, { noAck: true }); 897 | exchange1.stopConsumer().then(() => { 898 | cleanup(connection, done); 899 | }); 900 | }); 901 | 902 | it("should not generate an error when stopping a non existing Exchange consumer", (done) => { 903 | // initialize 904 | var connection = getAmqpConnection(); 905 | 906 | // test code 907 | var exchange1 = connection.declareExchange(nextExchangeName()); 908 | 909 | exchange1.activateConsumer((message) => { 910 | cleanup(connection, done, new Error("Received unexpected message")); 911 | }, { noAck: true }); 912 | exchange1.stopConsumer().then(() => { 913 | return exchange1.stopConsumer(); 914 | }).then(() => { 915 | cleanup(connection, done); 916 | }) 917 | .catch((err) => { 918 | cleanup(connection, done, err); 919 | }); 920 | }); 921 | 922 | it("should not generate an error when stopping a non existing Queue consumer", (done) => { 923 | // initialize 924 | var connection = getAmqpConnection(); 925 | 926 | // test code 927 | var queue = connection.declareQueue(nextQueueName()); 928 | 929 | queue.activateConsumer((message) => { 930 | cleanup(connection, done, new Error("Received unexpected message")); 931 | }, { noAck: true }); 932 | queue.stopConsumer().then(() => { 933 | return queue.stopConsumer(); 934 | }).then(() => { 935 | cleanup(connection, done); 936 | }) 937 | .catch((err) => { 938 | cleanup(connection, done, err); 939 | }); 940 | }); 941 | 942 | it("should send a message to a queue before the queue is explicitely initialized", (done) => { 943 | // initialize 944 | var connection = getAmqpConnection(); 945 | 946 | // test code 947 | var queue = connection.declareQueue(nextQueueName()); 948 | var msg = new Amqp.Message("Test"); 949 | 950 | queue.send(msg); 951 | 952 | queue.activateConsumer((message) => { 953 | try { 954 | expect(message.getContent()).equals("Test"); 955 | cleanup(connection, done); 956 | } catch (err) { 957 | cleanup(connection, done, err); 958 | } 959 | }, { noAck: true }); 960 | }); 961 | 962 | it("should accept optional parameters", (done) => { 963 | // initialize 964 | var connection = getAmqpConnection(); 965 | var messagesReceived = 0; 966 | 967 | // test code 968 | var exchange1 = connection.declareExchange(nextExchangeName(), "topic", { durable: true }); 969 | var exchange2 = connection.declareExchange(nextExchangeName(), "topic", { durable: true }); 970 | var queue = connection.declareQueue(nextQueueName(), { durable: true }); 971 | queue.bind(exchange1, "*.*", {}); 972 | exchange1.bind(exchange2, "*.test", {}); 973 | 974 | connection.completeConfiguration().then(() => { 975 | var msg = new Amqp.Message("ParameterTest", {}); 976 | exchange2.send(msg, "topic.test"); 977 | exchange1.send(msg, "topic.test2"); 978 | queue.send(msg); 979 | }); 980 | 981 | queue.activateConsumer((message) => { 982 | try { 983 | expect(message.getContent()).equals("ParameterTest"); 984 | messagesReceived++; 985 | //expect three messages 986 | if (messagesReceived === 3) { 987 | cleanup(connection, done); 988 | } 989 | } catch (err) { 990 | cleanup(connection, done, err); 991 | } 992 | }, { noAck: true }); 993 | }); 994 | 995 | it("should close an exchange and a queue", function (done) { 996 | // initialize 997 | var connection = getAmqpConnection(); 998 | var messagesReceived = 0; 999 | 1000 | // test code 1001 | var exchangeName = nextExchangeName(); 1002 | var queueName = nextQueueName(); 1003 | 1004 | var exchange = connection.declareExchange(exchangeName); 1005 | var queue = connection.declareQueue(queueName); 1006 | queue.bind(exchange); 1007 | 1008 | connection.completeConfiguration().then(function () { 1009 | exchange.publish("InQueueTest"); 1010 | exchange.close().then(function () { 1011 | return queue.close(); 1012 | }).then(function () { 1013 | queue = connection.declareQueue(queueName); 1014 | return queue.initialized; 1015 | }).then(function () { 1016 | exchange = connection.declareExchange(exchangeName); 1017 | return queue.initialized; 1018 | }).then((result) => { 1019 | expect(result.messageCount).equals(1); 1020 | cleanup(connection, done); 1021 | }).catch((err) => { 1022 | console.log(err); 1023 | cleanup(connection, done); 1024 | }); 1025 | }); 1026 | }); 1027 | 1028 | it("should delete an exchange and a queue", function (done) { 1029 | // initialize 1030 | var connection = getAmqpConnection(); 1031 | 1032 | // test code 1033 | var exchangeName = nextExchangeName(); 1034 | var queueName = nextQueueName(); 1035 | 1036 | var exchange = connection.declareExchange(exchangeName); 1037 | var queue = connection.declareQueue(queueName); 1038 | queue.bind(exchange); 1039 | 1040 | connection.completeConfiguration().then(function () { 1041 | exchange.publish("InQueueTest"); 1042 | exchange.delete().then(function () { 1043 | return queue.delete(); 1044 | }).then(function () { 1045 | queue = connection.declareQueue(queueName); 1046 | return queue.initialized; 1047 | }).then((result) => { 1048 | expect(result.messageCount).equals(0); 1049 | cleanup(connection, done); 1050 | }); 1051 | }); 1052 | }); 1053 | 1054 | it("should process a queue rpc", function (done) { 1055 | // initialize 1056 | var connection = getAmqpConnection(); 1057 | 1058 | // test code 1059 | var queue = connection.declareQueue(nextQueueName()); 1060 | 1061 | queue.activateConsumer((message) => { 1062 | return message.getContent().reply; 1063 | }); 1064 | 1065 | connection.completeConfiguration().then(function () { 1066 | queue.rpc({ reply: "TestRpc" }).then((result) => { 1067 | try { 1068 | expect(result.getContent()).equals("TestRpc"); 1069 | cleanup(connection, done); 1070 | } catch (err) { 1071 | cleanup(connection, done, err); 1072 | } 1073 | }); 1074 | }); 1075 | }); 1076 | 1077 | it("should process an unresolved queue rpc, consumer returning Message", function (done) { 1078 | // initialize 1079 | var connection = getAmqpConnection(); 1080 | 1081 | // test code 1082 | var queue = connection.declareQueue(nextQueueName()); 1083 | 1084 | queue.activateConsumer((message) => { 1085 | return new Amqp.Message(message.getContent().reply); 1086 | }); 1087 | 1088 | queue.rpc({ reply: "TestRpc" }).then((result) => { 1089 | try { 1090 | expect(result.getContent()).equals("TestRpc"); 1091 | cleanup(connection, done); 1092 | } catch (err) { 1093 | cleanup(connection, done, err); 1094 | } 1095 | }); 1096 | }); 1097 | 1098 | it("should process a queue rpc, consumer returning Promise", function (done) { 1099 | // initialize 1100 | var connection = getAmqpConnection(); 1101 | 1102 | // test code 1103 | var queue = connection.declareQueue(nextQueueName()); 1104 | 1105 | queue.activateConsumer((message) => { 1106 | return new Promise((resolve, reject) => { 1107 | setTimeout(() => { 1108 | resolve(message.getContent().reply); 1109 | }, 10); 1110 | }); 1111 | }); 1112 | 1113 | connection.completeConfiguration().then(function () { 1114 | queue.rpc({ reply: "TestRpc" }).then((result) => { 1115 | try { 1116 | expect(result.getContent()).equals("TestRpc"); 1117 | cleanup(connection, done); 1118 | } catch (err) { 1119 | cleanup(connection, done, err); 1120 | } 1121 | }); 1122 | }); 1123 | }); 1124 | 1125 | it("should process an exchange rpc", function (done) { 1126 | // initialize 1127 | var connection = getAmqpConnection(); 1128 | 1129 | // test code 1130 | var exchange = connection.declareExchange(nextExchangeName()); 1131 | 1132 | exchange.activateConsumer((message) => { 1133 | return message.getContent().reply; 1134 | }); 1135 | 1136 | connection.completeConfiguration().then(function () { 1137 | exchange.rpc({ reply: "TestRpc" }).then((result) => { 1138 | try { 1139 | expect(result.getContent()).equals("TestRpc"); 1140 | cleanup(connection, done); 1141 | } catch (err) { 1142 | cleanup(connection, done, err); 1143 | } 1144 | }); 1145 | }); 1146 | }); 1147 | 1148 | it("should create a topology and send and receive a Message", function (done) { 1149 | // initialize 1150 | var connection = getAmqpConnection(); 1151 | 1152 | // test code 1153 | var exchangeName1 = nextExchangeName(); 1154 | var exchangeName2 = nextExchangeName(); 1155 | var queueName1 = nextQueueName(); 1156 | var topology: Amqp.Connection.Topology = { 1157 | exchanges: [ 1158 | { name: exchangeName1 }, 1159 | { name: exchangeName2 } 1160 | ], 1161 | queues: [ 1162 | { name: queueName1 } 1163 | ], 1164 | bindings: [ 1165 | { source: exchangeName1, exchange: exchangeName2 }, 1166 | { source: exchangeName2, queue: queueName1 } 1167 | ] 1168 | }; 1169 | 1170 | connection.declareTopology(topology).then(function () { 1171 | var queue = connection.declareQueue(queueName1); 1172 | queue.activateConsumer((message) => { 1173 | expect(message.getContent()).equals("Test"); 1174 | cleanup(connection, done); 1175 | }, { noAck: true }); 1176 | 1177 | var exchange = connection.declareExchange(exchangeName1); 1178 | var msg = new Amqp.Message("Test"); 1179 | exchange.send(msg); 1180 | }, (err) => { // failed to configure the defined topology 1181 | done(err); 1182 | }); 1183 | }); 1184 | 1185 | it("should close a queue multiple times without generating errors", function (done) { 1186 | // initialize 1187 | var connection = getAmqpConnection(); 1188 | 1189 | // test code 1190 | var queueName = nextQueueName(); 1191 | var queue = connection.declareQueue(queueName); 1192 | 1193 | connection.completeConfiguration().then(function () { 1194 | queue.close(); 1195 | queue.close().then(() => { 1196 | // redeclare queue for correct cleanup 1197 | queue = connection.declareQueue(queueName); 1198 | queue.initialized.then(() => { 1199 | cleanup(connection, done); 1200 | }); 1201 | }); 1202 | }, (err) => { // failed to configure the defined topology 1203 | done(err); 1204 | }); 1205 | }); 1206 | 1207 | it("should delete a queue multiple times without generating errors", function (done) { 1208 | // initialize 1209 | var connection = getAmqpConnection(); 1210 | 1211 | // test code 1212 | var queueName = nextQueueName(); 1213 | var queue = connection.declareQueue(queueName); 1214 | 1215 | connection.completeConfiguration().then(function () { 1216 | queue.delete(); 1217 | queue.delete().then(() => { 1218 | cleanup(connection, done); 1219 | }); 1220 | }, (err) => { // failed to configure the defined topology 1221 | done(err); 1222 | }); 1223 | }); 1224 | 1225 | it("should close an exchange multiple times without generating errors", function (done) { 1226 | // initialize 1227 | var connection = getAmqpConnection(); 1228 | 1229 | // test code 1230 | var exchangeName = nextExchangeName(); 1231 | var exchange = connection.declareExchange(exchangeName); 1232 | 1233 | connection.completeConfiguration().then(function () { 1234 | exchange.close(); 1235 | exchange.close().then(() => { 1236 | // redeclare exchange for correct cleanup 1237 | exchange = connection.declareExchange(exchangeName); 1238 | exchange.initialized.then(() => { 1239 | cleanup(connection, done); 1240 | }); 1241 | }); 1242 | }, (err) => { // failed to configure the defined topology 1243 | done(err); 1244 | }); 1245 | }); 1246 | 1247 | it("should delete an exchange multiple times without generating errors", function (done) { 1248 | // initialize 1249 | var connection = getAmqpConnection(); 1250 | 1251 | // test code 1252 | var exchangeName = nextExchangeName(); 1253 | var exchange = connection.declareExchange(exchangeName); 1254 | 1255 | connection.completeConfiguration().then(function () { 1256 | exchange.delete(); 1257 | exchange.delete().then(() => { 1258 | cleanup(connection, done); 1259 | }); 1260 | }, (err) => { // failed to configure the defined topology 1261 | done(err); 1262 | }); 1263 | }); 1264 | 1265 | it("should set a prefetch count to a queue", function (done) { 1266 | // initialize 1267 | var connection = getAmqpConnection(); 1268 | 1269 | // test code 1270 | var queueName = nextQueueName(); 1271 | var queue = connection.declareQueue(queueName); 1272 | 1273 | connection.completeConfiguration().then(function () { 1274 | // todo: create a ral test that checks if the function works 1275 | queue.prefetch(3); 1276 | cleanup(connection, done); 1277 | }, (err) => { // failed to configure the defined topology 1278 | done(err); 1279 | }); 1280 | }); 1281 | 1282 | it("should recover to a queue", function (done) { 1283 | // initialize 1284 | var connection = getAmqpConnection(); 1285 | 1286 | // test code 1287 | var queueName = nextQueueName(); 1288 | var queue = connection.declareQueue(queueName); 1289 | 1290 | connection.completeConfiguration().then(function () { 1291 | // todo: create a real test that checks if the function works 1292 | queue.recover().then(() => { 1293 | cleanup(connection, done); 1294 | }); 1295 | }, (err) => { // failed to configure the defined topology 1296 | done(err); 1297 | }); 1298 | }); 1299 | 1300 | it("should not connect to a nonexisiting queue with 'noCreate: true'", function (done) { 1301 | // initialize 1302 | var connection = getAmqpConnection(); 1303 | 1304 | // test code 1305 | var queueName = nextQueueName(); 1306 | connection.declareQueue(queueName, { noCreate: true }); 1307 | 1308 | connection.completeConfiguration() 1309 | .then(() => { 1310 | cleanup(connection, done, new Error("Unexpected existing queue")); 1311 | }) 1312 | .catch((err) => { 1313 | expect(err.message).to.contain("NOT-FOUND"); 1314 | cleanup(connection, done); 1315 | }); 1316 | }); 1317 | 1318 | it("should connect to an exisiting queue with 'noCreate: true'", function (done) { 1319 | // initialize 1320 | var connection = getAmqpConnection(); 1321 | 1322 | // test code 1323 | var queueName = nextQueueName(); 1324 | connection.declareQueue(queueName); 1325 | 1326 | connection.completeConfiguration() 1327 | .then(() => { 1328 | var queue = connection.declareQueue(queueName, { noCreate: true }); 1329 | queue.initialized 1330 | .then(() => { 1331 | cleanup(connection, done); 1332 | }) 1333 | .catch((err) => { 1334 | cleanup(connection, done, err); 1335 | }); 1336 | }); 1337 | }); 1338 | 1339 | it("should not connect to a nonexisiting exchange with 'noCreate: true'", function (done) { 1340 | // initialize 1341 | var connection = getAmqpConnection(); 1342 | 1343 | // test code 1344 | var exchangeName = nextExchangeName(); 1345 | connection.declareExchange(exchangeName, "", { noCreate: true }); 1346 | 1347 | connection.completeConfiguration() 1348 | .then(() => { 1349 | cleanup(connection, done, new Error("Unexpected existing exchange: " + exchangeName)); 1350 | }) 1351 | .catch((err) => { 1352 | expect(err.message).to.contain("NOT-FOUND"); 1353 | cleanup(connection, done); 1354 | }); 1355 | }); 1356 | 1357 | it("should connect to an exisiting exchange with 'noCreate: true'", function (done) { 1358 | // initialize 1359 | var connection = getAmqpConnection(); 1360 | 1361 | var exchangeName = nextExchangeName(); 1362 | AmqpLib.connect(ConnectionUrl) 1363 | .then((conn) => { 1364 | return conn.createChannel(); 1365 | }) 1366 | .then((ch) => { 1367 | return ch.assertExchange(exchangeName, "fanout"); 1368 | }) 1369 | .then(() => { 1370 | var exchange = connection.declareExchange(exchangeName, "", { noCreate: true }); 1371 | exchange.initialized 1372 | .then(() => { 1373 | cleanup(connection, done); 1374 | }) 1375 | .catch((err) => { 1376 | cleanup(connection, done, err); 1377 | }); 1378 | }) 1379 | .catch((err) => { 1380 | done(err); 1381 | }); 1382 | }); 1383 | }); 1384 | }); 1385 | -------------------------------------------------------------------------------- /src/amqp-ts.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * AmqpSimple.ts - provides a simple interface to read from and write to RabbitMQ amqp exchanges 3 | * Created by Ab on 17-9-2015. 4 | * 5 | * methods and properties starting with '_' signify that the scope of the item should be limited to 6 | * the inside of the enclosing namespace. 7 | */ 8 | 9 | // simplified use of amqp exchanges and queues, wrapper for amqplib 10 | 11 | import * as AmqpLib from "amqplib/callback_api"; 12 | import * as Promise from "bluebird"; 13 | import { logger } from "./logger"; 14 | import * as path from "path"; 15 | import * as os from "os"; 16 | import { EventEmitter } from "events"; 17 | 18 | var ApplicationName = process.env.AMQPTS_APPLICATIONNAME || 19 | (path.parse ? path.parse(process.argv[1]).name : path.basename(process.argv[1])); 20 | 21 | export var log = logger.jsonLogger(process.env.AMQPTS_LOGLEVEL || "error"); 22 | 23 | // name for the RabbitMQ direct reply-to queue 24 | const DIRECT_REPLY_TO_QUEUE = "amq.rabbitmq.reply-to"; 25 | 26 | //---------------------------------------------------------------------------------------------------- 27 | // Connection class 28 | //---------------------------------------------------------------------------------------------------- 29 | export class Connection extends EventEmitter { 30 | initialized: Promise; 31 | 32 | private url: string; 33 | private socketOptions: any; 34 | private reconnectStrategy: Connection.ReconnectStrategy; 35 | private connectedBefore = false; 36 | private rebuildPromise: Promise | undefined; 37 | 38 | _connection: AmqpLib.Connection; 39 | _retry: number; 40 | _rebuilding: boolean = false; 41 | _isClosing: boolean = false; 42 | 43 | public isConnected: boolean = false; 44 | 45 | _exchanges: { [id: string]: Exchange }; 46 | _queues: { [id: string]: Queue }; 47 | _bindings: { [id: string]: Binding }; 48 | 49 | constructor(url = "amqp://localhost", 50 | socketOptions: any = {}, 51 | reconnectStrategy: Connection.ReconnectStrategy = { retries: 0, interval: 1500 }) { 52 | super(); 53 | 54 | this.url = url; 55 | this.socketOptions = socketOptions; 56 | this.reconnectStrategy = reconnectStrategy; 57 | this._exchanges = {}; 58 | this._queues = {}; 59 | this._bindings = {}; 60 | 61 | this.rebuildConnection(); 62 | } 63 | 64 | private rebuildConnection(): Promise { 65 | if (this._rebuilding) { // only one rebuild process can be active at any time 66 | log.debug("Connection rebuild already in progress, joining active rebuild attempt.", { module: "amqp-ts" }); 67 | return this.initialized; 68 | } 69 | this._retry = -1; 70 | this._rebuilding = true; 71 | this._isClosing = false; 72 | 73 | // rebuild the connection 74 | this.initialized = new Promise((resolve, reject) => { 75 | this.tryToConnect(this, 0, (err) => { 76 | /* istanbul ignore if */ 77 | if (err) { 78 | this._rebuilding = false; 79 | reject(err); 80 | } else { 81 | this._rebuilding = false; 82 | if (this.connectedBefore) { 83 | log.warn( "Connection re-established", { module: "amqp-ts" }); 84 | this.emit("re_established_connection"); 85 | } else { 86 | log.info( "Connection established.", { module: "amqp-ts" }); 87 | this.emit("open_connection"); 88 | this.connectedBefore = true; 89 | } 90 | resolve(null); 91 | } 92 | }); 93 | }); 94 | /* istanbul ignore next */ 95 | this.initialized.catch((error) => { 96 | log.warn("Error creating connection!", { module: "amqp-ts", error }); 97 | this.emit("error_connection", error); 98 | 99 | //throw (err); 100 | }); 101 | 102 | return this.initialized; 103 | } 104 | 105 | private tryToConnect(thisConnection: Connection, retry: number, callback: (err: any) => void): void { 106 | AmqpLib.connect(thisConnection.url, thisConnection.socketOptions, (error, connection) => { 107 | /* istanbul ignore if */ 108 | if (error) { 109 | thisConnection.isConnected = false; 110 | // only do every retry once, amqplib can return multiple connection errors for one connection request (error?) 111 | if (retry <= this._retry) { 112 | //amqpts_log.log("warn" , "Double retry " + retry + ", skipping.", {module: "amqp-ts"}); 113 | return; 114 | } 115 | 116 | log.warn("Connection failed.", { module: "amqp-ts", error }); 117 | 118 | this._retry = retry; 119 | if (thisConnection.reconnectStrategy.retries === 0 || thisConnection.reconnectStrategy.retries > retry) { 120 | log.warn("Connection retry " + (retry + 1) + " in " + thisConnection.reconnectStrategy.interval + "ms", 121 | { module: "amqp-ts" }); 122 | thisConnection.emit("trying_connect"); 123 | 124 | setTimeout(thisConnection.tryToConnect, 125 | thisConnection.reconnectStrategy.interval, 126 | thisConnection, 127 | retry + 1, 128 | callback 129 | ); 130 | } else { //no reconnect strategy, or retries exhausted, so return the error 131 | log.warn("Connection failed, exiting: No connection retries left (retry " + retry + ").", { module: "amqp-ts" }); 132 | callback(error); 133 | } 134 | } else { 135 | var restart = (error: Error) => { 136 | log.debug("Connection error occurred.", { module: "amqp-ts", error }); 137 | connection.removeListener("error", restart); 138 | 139 | //connection.removeListener("end", restart); // not sure this is needed 140 | thisConnection._rebuildAll(error); //try to rebuild the topology when the connection unexpectedly closes 141 | }; 142 | var onClose = () => { 143 | connection.removeListener("close", onClose); 144 | if (!thisConnection._rebuilding && !thisConnection._isClosing) { 145 | thisConnection.emit("lost_connection"); 146 | restart(new Error("Connection closed by remote host")); 147 | } 148 | 149 | }; 150 | connection.on("error", restart); 151 | connection.on("close", onClose); 152 | //connection.on("end", restart); // not sure this is needed 153 | thisConnection._connection = connection; 154 | thisConnection.isConnected = true; 155 | 156 | callback(null); 157 | } 158 | }); 159 | } 160 | 161 | _rebuildAll(error: Error): Promise { 162 | if (this.rebuildPromise) { 163 | return this.rebuildPromise; 164 | } 165 | 166 | this.rebuildPromise = Promise.resolve().then(() => { 167 | log.warn("Connection error: " + error.message, { module: "amqp-ts", error }); 168 | 169 | log.debug("Rebuilding connection NOW.", { module: "amqp-ts" }); 170 | this.rebuildConnection(); 171 | 172 | //re initialize exchanges, queues and bindings if they exist 173 | for (var exchangeId in this._exchanges) { 174 | var exchange = this._exchanges[exchangeId]; 175 | log.debug("Re-initialize Exchange '" + exchange._name + "'.", { module: "amqp-ts" }); 176 | exchange._initialize(); 177 | } 178 | for (var queueId in this._queues) { 179 | var queue = this._queues[queueId]; 180 | var consumer = queue._consumer; 181 | log.debug("Re-initialize queue '" + queue._name + "'.", { module: "amqp-ts" }); 182 | queue._initialize(); 183 | if (consumer) { 184 | log.debug("Re-initialize consumer for queue '" + queue._name + "'.", { module: "amqp-ts" }); 185 | queue._initializeConsumer(); 186 | } 187 | } 188 | for (var bindingId in this._bindings) { 189 | var binding = this._bindings[bindingId]; 190 | log.debug("Re-initialize binding from '" + binding._source._name + "' to '" + 191 | binding._destination._name + "'.", { module: "amqp-ts" }); 192 | binding._initialize(); 193 | } 194 | 195 | return new Promise((resolve, reject) => { 196 | this.completeConfiguration().then(() => { 197 | log.debug("Rebuild success.", { module: "amqp-ts" }); 198 | resolve(null); 199 | }, /* istanbul ignore next */ 200 | (rejectReason) => { 201 | log.debug("Rebuild failed.", { module: "amqp-ts" }); 202 | reject(rejectReason); 203 | }); 204 | }); 205 | }) 206 | .then(() => { 207 | this.rebuildPromise = undefined; 208 | }) 209 | .catch((e) => { 210 | this.rebuildPromise = undefined; 211 | throw e; 212 | }); 213 | 214 | return this.rebuildPromise; 215 | } 216 | 217 | close(): Promise { 218 | this._isClosing = true; 219 | return new Promise((resolve, reject) => { 220 | this.initialized.then(() => { 221 | this._connection.close(err => { 222 | /* istanbul ignore if */ 223 | if (err) { 224 | reject(err); 225 | } else { 226 | this.isConnected = false; 227 | this.emit("close_connection"); 228 | resolve(null); 229 | } 230 | }); 231 | }); 232 | }); 233 | } 234 | 235 | /** 236 | * Make sure the whole defined connection topology is configured: 237 | * return promise that fulfills after all defined exchanges, queues and bindings are initialized 238 | */ 239 | completeConfiguration(): Promise { 240 | var promises: Promise[] = []; 241 | for (var exchangeId in this._exchanges) { 242 | var exchange: Exchange = this._exchanges[exchangeId]; 243 | promises.push(exchange.initialized); 244 | } 245 | for (var queueId in this._queues) { 246 | var queue: Queue = this._queues[queueId]; 247 | promises.push(queue.initialized); 248 | if (queue._consumerInitialized) { 249 | promises.push(queue._consumerInitialized); 250 | } 251 | } 252 | for (var bindingId in this._bindings) { 253 | var binding: Binding = this._bindings[bindingId]; 254 | promises.push(binding.initialized); 255 | } 256 | return Promise.all(promises); 257 | } 258 | 259 | /** 260 | * Delete the whole defined connection topology: 261 | * return promise that fulfills after all defined exchanges, queues and bindings have been removed 262 | */ 263 | deleteConfiguration(): Promise { 264 | var promises: Promise[] = []; 265 | for (var bindingId in this._bindings) { 266 | var binding: Binding = this._bindings[bindingId]; 267 | promises.push(binding.delete()); 268 | } 269 | for (var queueId in this._queues) { 270 | var queue: Queue = this._queues[queueId]; 271 | if (queue._consumerInitialized) { 272 | promises.push(queue.stopConsumer()); 273 | } 274 | promises.push(queue.delete()); 275 | } 276 | for (var exchangeId in this._exchanges) { 277 | var exchange: Exchange = this._exchanges[exchangeId]; 278 | promises.push(exchange.delete()); 279 | } 280 | return Promise.all(promises); 281 | } 282 | 283 | declareExchange(name: string, type?: string, options?: Exchange.DeclarationOptions): Exchange { 284 | var exchange = this._exchanges[name]; 285 | if (exchange === undefined) { 286 | exchange = new Exchange(this, name, type, options); 287 | } 288 | return exchange; 289 | } 290 | 291 | declareQueue(name: string, options?: Queue.DeclarationOptions): Queue { 292 | var queue = this._queues[name]; 293 | if (queue === undefined) { 294 | queue = new Queue(this, name, options); 295 | } 296 | return queue; 297 | } 298 | 299 | declareTopology(topology: Connection.Topology): Promise { 300 | var promises: Promise[] = []; 301 | var i: number; 302 | var len: number; 303 | 304 | if (topology.exchanges !== undefined) { 305 | for (i = 0, len = topology.exchanges.length; i < len; i++) { 306 | var exchange = topology.exchanges[i]; 307 | promises.push(this.declareExchange(exchange.name, exchange.type, exchange.options).initialized); 308 | } 309 | } 310 | if (topology.queues !== undefined) { 311 | for (i = 0, len = topology.queues.length; i < len; i++) { 312 | var queue = topology.queues[i]; 313 | promises.push(this.declareQueue(queue.name, queue.options).initialized); 314 | } 315 | } 316 | if (topology.bindings !== undefined) { 317 | for (i = 0, len = topology.bindings.length; i < len; i++) { 318 | var binding = topology.bindings[i]; 319 | var source = this.declareExchange(binding.source); 320 | var destination: Queue | Exchange; 321 | if (binding.exchange !== undefined) { 322 | destination = this.declareExchange(binding.exchange); 323 | } else { 324 | destination = this.declareQueue(binding.queue); 325 | } 326 | promises.push(destination.bind(source, binding.pattern, binding.args)); 327 | } 328 | } 329 | return Promise.all(promises); 330 | } 331 | 332 | get getConnection(): AmqpLib.Connection { 333 | return this._connection; 334 | } 335 | } 336 | export namespace Connection { 337 | "use strict"; 338 | export interface ReconnectStrategy { 339 | retries: number; // number of retries, 0 is forever 340 | interval: number; // retry interval in ms 341 | } 342 | export interface Topology { 343 | exchanges: { name: string, type?: string, options?: any }[]; 344 | queues: { name: string, options?: any }[]; 345 | bindings: { source: string, queue?: string, exchange?: string, pattern?: string, args?: any }[]; 346 | } 347 | } 348 | 349 | 350 | //---------------------------------------------------------------------------------------------------- 351 | // Message class 352 | //---------------------------------------------------------------------------------------------------- 353 | export class Message { 354 | content: Buffer; 355 | fields: any; 356 | properties: any; 357 | 358 | _channel: AmqpLib.ConfirmChannel; // for received messages only: the channel it has been received on 359 | _message: AmqpLib.Message; // received messages only: original amqplib message 360 | 361 | constructor(content?: any, options: any = {}) { 362 | this.properties = options; 363 | if (content !== undefined) { 364 | this.setContent(content); 365 | } 366 | } 367 | 368 | setContent(content: any): void { 369 | if (typeof content === "string") { 370 | this.content = new Buffer(content); 371 | } else if (!(content instanceof Buffer)) { 372 | this.content = new Buffer(JSON.stringify(content)); 373 | this.properties.contentType = "application/json"; 374 | } else { 375 | this.content = content; 376 | } 377 | } 378 | 379 | getContent(): any { 380 | var content = this.content.toString(); 381 | if (this.properties.contentType === "application/json") { 382 | content = JSON.parse(content); 383 | } 384 | return content; 385 | } 386 | 387 | sendTo(destination: Exchange | Queue, routingKey: string = ""): Promise { 388 | // inline function to send the message 389 | var sendMessage = () => { 390 | return new Promise((resolve, reject) => { 391 | destination._channel.publish(exchange, routingKey, this.content, this.properties, (err, data) => { 392 | if (!err) { 393 | resolve(data); 394 | } else { 395 | reject(err); 396 | } 397 | }); 398 | }) 399 | .catch((error) => { 400 | exports.log.debug("Publish error: " + error.message, { module: "amqp-ts", error }); 401 | var connection = destination._connection; 402 | exports.log.debug("Try to rebuild connection, before Call.", { module: "amqp-ts" }); 403 | 404 | connection._rebuildAll(error).then(() => { 405 | exports.log.warn("Connection rebuilt, NOT retransmitting message.", { module: "amqp-ts" }); 406 | }); 407 | return Promise.reject(error); 408 | }); 409 | }; 410 | 411 | var exchange: string; 412 | if (destination instanceof Queue) { 413 | exchange = ""; 414 | routingKey = destination._name; 415 | } else { 416 | exchange = destination._name; 417 | } 418 | 419 | // execute sync when possible 420 | if (destination.initialized.isFulfilled()) { 421 | return sendMessage(); 422 | } else { 423 | return Promise.reject(new Error("Connection is not ready.")); 424 | } 425 | } 426 | 427 | ack(allUpTo?: boolean): void { 428 | if (this._channel !== undefined) { 429 | this._channel.ack(this._message, allUpTo); 430 | } 431 | } 432 | 433 | nack(allUpTo?: boolean, requeue?: boolean): void { 434 | if (this._channel !== undefined) { 435 | this._channel.nack(this._message, allUpTo, requeue); 436 | } 437 | } 438 | 439 | reject(requeue = false): void { 440 | if (this._channel !== undefined) { 441 | this._channel.reject(this._message, requeue); 442 | } 443 | } 444 | } 445 | 446 | 447 | //---------------------------------------------------------------------------------------------------- 448 | // Exchange class 449 | //---------------------------------------------------------------------------------------------------- 450 | export class Exchange { 451 | initialized: Promise; 452 | 453 | _consumer_handlers: Array<[string, any]> = new Array<[string, any]>(); 454 | _isConsumerInitializedRcp: boolean = false; 455 | 456 | _connection: Connection; 457 | _channel: AmqpLib.ConfirmChannel; 458 | _name: string; 459 | _type: string; 460 | _options: Exchange.DeclarationOptions; 461 | 462 | _deleting: Promise; 463 | _closing: Promise; 464 | 465 | get name() { 466 | return this._name; 467 | } 468 | 469 | get type() { 470 | return this._type; 471 | } 472 | 473 | constructor(connection: Connection, name: string, type?: string, options: Exchange.DeclarationOptions = {}) { 474 | this._connection = connection; 475 | this._name = name; 476 | this._type = type; 477 | this._options = options; 478 | this._initialize(); 479 | } 480 | 481 | _initialize() { 482 | this.initialized = new Promise((resolve, reject) => { 483 | this._connection.initialized.then(() => { 484 | this._connection._connection.createConfirmChannel((error, channel) => { 485 | /* istanbul ignore if */ 486 | if (error) { 487 | log.error( "Failed on channel creation: " + error.message, { module: "amqp-ts", error }); 488 | reject(error); 489 | } else { 490 | log.info("Channel created.", { module: "amqp-ts" }); 491 | this._channel = channel; 492 | let callback = (err, ok) => { 493 | /* istanbul ignore if */ 494 | if (err) { 495 | log.error("Failed to create exchange '" + this._name + "'.", { module: "amqp-ts", error: err }); 496 | delete this._connection._exchanges[this._name]; 497 | reject(err); 498 | } else { 499 | resolve(ok); 500 | } 501 | }; 502 | if (this._options.noCreate) { 503 | this._channel.checkExchange(this._name, callback); 504 | } else { 505 | this._channel.assertExchange(this._name, this._type, this._options, callback); 506 | } 507 | } 508 | }); 509 | }).catch((error) => { 510 | log.warn("Channel failure, error caused during connection!", { module: "amqp-ts", error }); 511 | }); 512 | }); 513 | this._connection._exchanges[this._name] = this; 514 | } 515 | 516 | waitForConfirms() { 517 | const channel = this._channel; 518 | 519 | var waitForChannelConfirms = () => { 520 | return new Promise(function (resolve, reject) { 521 | channel.waitForConfirms((err) => { 522 | if (!err) { 523 | resolve(null); 524 | } else { 525 | reject(err); 526 | } 527 | }); 528 | }); 529 | }; 530 | 531 | if (this.initialized.isFulfilled()) { 532 | return waitForChannelConfirms(); 533 | } else { 534 | return this.initialized.then(waitForChannelConfirms); 535 | } 536 | } 537 | 538 | /** 539 | * deprecated, use 'exchange.send(message: Message, routingKey?: string)' instead 540 | */ 541 | publish(content: any, routingKey = "", options: any = {}): Promise { 542 | if (typeof content === "string") { 543 | content = new Buffer(content); 544 | } else if (!(content instanceof Buffer)) { 545 | content = new Buffer(JSON.stringify(content)); 546 | options.contentType = options.contentType || "application/json"; 547 | } 548 | return this.initialized.then(() => { 549 | return new Promise((resolve, reject) => { 550 | this._channel.publish(this._name, routingKey, content, options, (err, data) => { 551 | if (!err) { 552 | resolve(data); 553 | } else { 554 | reject(err); 555 | } 556 | }); 557 | }).catch((error) => { 558 | log.warn("Exchange publish error: " + error.message, { module: "amqp-ts", error }); 559 | var connection = this._connection; 560 | connection._rebuildAll(error).then(() => { 561 | log.warn("Connection rebuilt, NOT retransmitting message.", { module: "amqp-ts" }); 562 | }); 563 | return Promise.reject(error); 564 | }); 565 | }); 566 | } 567 | 568 | send(message: Message, routingKey = ""): Promise { 569 | return message.sendTo(this, routingKey); 570 | } 571 | 572 | rpc(requestParameters: any, routingKey = "", callback?: (err, message: Message) => void): Promise { 573 | return new Promise((resolve, reject) => { 574 | 575 | function generateUuid(): string { 576 | return Math.random().toString() + 577 | Math.random().toString() + 578 | Math.random().toString(); 579 | } 580 | 581 | var processRpc = () => { 582 | var uuid: string = generateUuid(); 583 | if (!this._isConsumerInitializedRcp) { 584 | this._isConsumerInitializedRcp = true; 585 | this._channel.consume(DIRECT_REPLY_TO_QUEUE, (resultMsg) => { 586 | 587 | var result = new Message(resultMsg.content, resultMsg.fields); 588 | result.fields = resultMsg.fields; 589 | 590 | for (let handler of this._consumer_handlers) { 591 | if (handler[0] === resultMsg.properties.correlationId) { 592 | let func: Function = handler[1]; 593 | func.apply("", [undefined, result]); 594 | } 595 | } 596 | 597 | }, { noAck: true }, (err, ok) => { 598 | /* istanbul ignore if */ 599 | if (err) { 600 | reject(new Error("amqp-ts: Queue.rpc error: " + err.message)); 601 | } else { 602 | // send the rpc request 603 | this._consumer_handlers.push([uuid, callback]); 604 | // consumerTag = ok.consumerTag; 605 | var message = new Message(requestParameters, { correlationId: uuid, replyTo: DIRECT_REPLY_TO_QUEUE }); 606 | message.sendTo(this, routingKey); 607 | } 608 | }); 609 | } else { 610 | this._consumer_handlers.push([uuid, callback]); 611 | var message = new Message(requestParameters, { correlationId: uuid, replyTo: DIRECT_REPLY_TO_QUEUE }); 612 | message.sendTo(this, routingKey); 613 | } 614 | 615 | }; 616 | 617 | // execute sync when possible 618 | if (this.initialized.isFulfilled()) { 619 | processRpc(); 620 | } else { 621 | this.initialized.then(processRpc); 622 | } 623 | }); 624 | } 625 | 626 | delete(): Promise { 627 | if (this._deleting === undefined) { 628 | this._deleting = new Promise((resolve, reject) => { 629 | this.initialized.then(() => { 630 | return Binding.removeBindingsContaining(this); 631 | }).then(() => { 632 | this._channel.deleteExchange(this._name, {}, (err, ok) => { 633 | /* istanbul ignore if */ 634 | if (err) { 635 | reject(err); 636 | } else { 637 | this._channel.close((err) => { 638 | delete this.initialized; // invalidate exchange 639 | delete this._connection._exchanges[this._name]; // remove the exchange from our administration 640 | /* istanbul ignore if */ 641 | if (err) { 642 | reject(err); 643 | } else { 644 | log.info("Channel closed.", { module: "amqp-ts" }); 645 | delete this._channel; 646 | delete this._connection; 647 | resolve(null); 648 | } 649 | }); 650 | } 651 | }); 652 | }).catch((err) => { 653 | reject(err); 654 | }); 655 | }); 656 | } 657 | return this._deleting; 658 | } 659 | 660 | close(): Promise { 661 | if (this._closing === undefined) { 662 | this._closing = new Promise((resolve, reject) => { 663 | this.initialized.then(() => { 664 | return Binding.removeBindingsContaining(this); 665 | }).then(() => { 666 | delete this.initialized; // invalidate exchange 667 | delete this._connection._exchanges[this._name]; // remove the exchange from our administration 668 | this._channel.close((err) => { 669 | /* istanbul ignore if */ 670 | if (err) { 671 | reject(err); 672 | } else { 673 | log.info("Channel closed.", { module: "amqp-ts" }); 674 | delete this._channel; 675 | delete this._connection; 676 | resolve(null); 677 | } 678 | }); 679 | }).catch((err) => { 680 | reject(err); 681 | }); 682 | }); 683 | } 684 | return this._closing; 685 | } 686 | 687 | bind(source: Exchange, pattern = "", args: any = {}): Promise { 688 | var binding = new Binding(this, source, pattern, args); 689 | return binding.initialized; 690 | } 691 | 692 | unbind(source: Exchange, pattern = "", args: any = {}): Promise { 693 | return this._connection._bindings[Binding.id(this, source, pattern)].delete(); 694 | } 695 | 696 | consumerQueueName(): string { 697 | return this._name + "." + ApplicationName + "." + os.hostname() + "." + process.pid; 698 | } 699 | 700 | /** 701 | * deprecated, use 'exchange.activateConsumer(...)' instead 702 | */ 703 | startConsumer(onMessage: (msg: any, channel?: AmqpLib.Channel) => any, options?: Queue.StartConsumerOptions): Promise { 704 | var queueName = this.consumerQueueName(); 705 | if (this._connection._queues[queueName]) { 706 | return new Promise((_, reject) => { 707 | reject(new Error("amqp-ts Exchange.startConsumer error: consumer already defined")); 708 | }); 709 | } else { 710 | var promises: Promise[] = []; 711 | var queue = this._connection.declareQueue(queueName, { durable: false }); 712 | promises.push(queue.initialized); 713 | var binding = queue.bind(this); 714 | promises.push(binding); 715 | var consumer = queue.startConsumer(onMessage, options); 716 | promises.push(consumer); 717 | 718 | return Promise.all(promises); 719 | } 720 | } 721 | 722 | activateConsumer(onMessage: (msg: Message) => any, options?: Queue.ActivateConsumerOptions): Promise { 723 | var queueName = this.consumerQueueName(); 724 | if (this._connection._queues[queueName]) { 725 | return new Promise((_, reject) => { 726 | reject(new Error("amqp-ts Exchange.activateConsumer error: consumer already defined")); 727 | }); 728 | } else { 729 | var promises: Promise[] = []; 730 | var queue = this._connection.declareQueue(queueName, { durable: false }); 731 | promises.push(queue.initialized); 732 | var binding = queue.bind(this); 733 | promises.push(binding); 734 | var consumer = queue.activateConsumer(onMessage, options); 735 | promises.push(consumer); 736 | 737 | return Promise.all(promises); 738 | } 739 | } 740 | 741 | stopConsumer(): Promise { 742 | var queue = this._connection._queues[this.consumerQueueName()]; 743 | if (queue) { 744 | return queue.delete(); 745 | } else { 746 | return Promise.resolve(); 747 | } 748 | } 749 | } 750 | export namespace Exchange { 751 | "use strict"; 752 | export interface DeclarationOptions { 753 | durable?: boolean; 754 | internal?: boolean; 755 | autoDelete?: boolean; 756 | alternateExchange?: string; 757 | arguments?: any; 758 | noCreate?: boolean; 759 | } 760 | export interface InitializeResult { 761 | exchange: string; 762 | } 763 | } 764 | 765 | 766 | //---------------------------------------------------------------------------------------------------- 767 | // Queue class 768 | //---------------------------------------------------------------------------------------------------- 769 | export class Queue { 770 | initialized: Promise; 771 | 772 | _connection: Connection; 773 | _channel: AmqpLib.ConfirmChannel; 774 | _name: string; 775 | _options: Queue.DeclarationOptions; 776 | 777 | _consumer: (msg: any, channel?: AmqpLib.Channel) => any; 778 | _isStartConsumer: boolean; 779 | _rawConsumer: boolean; 780 | _consumerOptions: Queue.StartConsumerOptions; 781 | _consumerTag: string; 782 | _consumerInitialized: Promise; 783 | _consumerStopping: boolean; 784 | _deleting: Promise; 785 | _closing: Promise; 786 | 787 | get name() { 788 | return this._name; 789 | } 790 | 791 | constructor(connection: Connection, name: string, options: Queue.DeclarationOptions = {}) { 792 | this._connection = connection; 793 | this._name = name; 794 | this._options = options; 795 | this._connection._queues[this._name] = this; 796 | this._initialize(); 797 | } 798 | 799 | _initialize(): void { 800 | this.initialized = new Promise((resolve, reject) => { 801 | this._connection.initialized.then(() => { 802 | this._connection._connection.createConfirmChannel((error, channel) => { 803 | /* istanbul ignore if */ 804 | if (error) { 805 | log.error("Failed on channel creation: " + error.message, { module: "amqp-ts", error }); 806 | reject(error); 807 | } else { 808 | log.info("Channel created.", { module: "amqp-ts" }); 809 | this._channel = channel; 810 | let callback = (err, ok) => { 811 | /* istanbul ignore if */ 812 | if (err) { 813 | log.error("Failed to create queue '" + this._name + "'.", { module: "amqp-ts", error: err }); 814 | delete this._connection._queues[this._name]; 815 | reject(err); 816 | } else { 817 | if (this._options.prefetch) { 818 | this._channel.prefetch(this._options.prefetch); 819 | } 820 | resolve(ok); 821 | } 822 | }; 823 | 824 | if (this._options.noCreate) { 825 | this._channel.checkQueue(this._name, callback); 826 | } else { 827 | this._channel.assertQueue(this._name, this._options, callback); 828 | } 829 | } 830 | }); 831 | }).catch((error) => { 832 | log.warn("Channel failure, error caused during connection!", { module: "amqp-ts", error }); 833 | }); 834 | }); 835 | } 836 | 837 | static _packMessageContent(content: any, options: any): Buffer { 838 | if (typeof content === "string") { 839 | content = new Buffer(content); 840 | } else if (!(content instanceof Buffer)) { 841 | content = new Buffer(JSON.stringify(content)); 842 | options.contentType = "application/json"; 843 | } 844 | return content; 845 | } 846 | 847 | static _unpackMessageContent(msg: AmqpLib.Message): any { 848 | var content = msg.content.toString(); 849 | if (msg.properties.contentType === "application/json") { 850 | content = JSON.parse(content); 851 | } 852 | return content; 853 | } 854 | 855 | /** 856 | * deprecated, use 'queue.send(message: Message)' instead 857 | */ 858 | publish(content: any, options: any = {}): Promise { 859 | // inline function to send the message 860 | var sendMessage = () => { 861 | return new Promise((resolve, reject) => { 862 | this._channel.sendToQueue(this._name, content, options, (err, data) => { 863 | if (!err) { 864 | resolve(data); 865 | } else { 866 | reject(err); 867 | } 868 | }); 869 | }).catch((error) => { 870 | log.debug( "Queue publish error: " + error.message, { module: "amqp-ts", error }); 871 | var connection = this._connection; 872 | log.debug("Try to rebuild connection, before Call.", { module: "amqp-ts" }); 873 | connection._rebuildAll(error).then(() => { 874 | log.warn("Connection rebuilt, NOT retransmitting message.", { module: "amqp-ts" }); 875 | }); 876 | return Promise.reject(error); 877 | }); 878 | }; 879 | 880 | content = Queue._packMessageContent(content, options); 881 | // execute sync when possible 882 | if (this.initialized.isFulfilled()) { 883 | return sendMessage(); 884 | } else { 885 | return Promise.reject(new Error("Connection is not ready.")); 886 | } 887 | } 888 | 889 | send(message: Message): void { 890 | message.sendTo(this); 891 | } 892 | 893 | rpc(requestParameters: any): Promise { 894 | return new Promise((resolve, reject) => { 895 | var processRpc = () => { 896 | var consumerTag: string; 897 | this._channel.consume(DIRECT_REPLY_TO_QUEUE, (resultMsg) => { 898 | this._channel.cancel(consumerTag); 899 | var result = new Message(resultMsg.content, resultMsg.fields); 900 | result.fields = resultMsg.fields; 901 | resolve(result); 902 | }, { noAck: true }, (err, ok) => { 903 | /* istanbul ignore if */ 904 | if (err) { 905 | reject(new Error("amqp-ts: Queue.rpc error: " + err.message)); 906 | } else { 907 | // send the rpc request 908 | consumerTag = ok.consumerTag; 909 | var message = new Message(requestParameters, { replyTo: DIRECT_REPLY_TO_QUEUE }); 910 | message.sendTo(this); 911 | } 912 | }); 913 | }; 914 | 915 | // execute sync when possible 916 | if (this.initialized.isFulfilled()) { 917 | processRpc(); 918 | } else { 919 | this.initialized.then(processRpc); 920 | } 921 | }); 922 | } 923 | 924 | prefetch(count: number): void { 925 | this.initialized.then(() => { 926 | this._channel.prefetch(count); 927 | this._options.prefetch = count; 928 | }); 929 | } 930 | 931 | recover(): Promise { 932 | return new Promise((resolve, reject) => { 933 | this.initialized.then(() => { 934 | this._channel.recover((err, ok) => { 935 | if (err) { 936 | reject(err); 937 | } else { 938 | resolve(null); 939 | } 940 | }); 941 | }); 942 | }); 943 | } 944 | 945 | /** 946 | * deprecated, use 'queue.activateConsumer(...)' instead 947 | */ 948 | startConsumer(onMessage: (msg: any, channel?: AmqpLib.Channel) => any, 949 | options: Queue.StartConsumerOptions = {}) 950 | : Promise { 951 | if (this._consumerInitialized) { 952 | return new Promise((_, reject) => { 953 | reject(new Error("amqp-ts Queue.startConsumer error: consumer already defined")); 954 | }); 955 | } 956 | 957 | this._isStartConsumer = true; 958 | this._rawConsumer = (options.rawMessage === true); 959 | delete options.rawMessage; // remove to avoid possible problems with amqplib 960 | this._consumerOptions = options; 961 | this._consumer = onMessage; 962 | this._initializeConsumer(); 963 | 964 | return this._consumerInitialized; 965 | } 966 | 967 | activateConsumer(onMessage: (msg: Message) => any, 968 | options: Queue.ActivateConsumerOptions = {}) 969 | : Promise { 970 | if (this._consumerInitialized) { 971 | return new Promise((_, reject) => { 972 | reject(new Error("amqp-ts Queue.activateConsumer error: consumer already defined")); 973 | }); 974 | } 975 | 976 | this._consumerOptions = options; 977 | this._consumer = onMessage; 978 | this._initializeConsumer(); 979 | 980 | return this._consumerInitialized; 981 | } 982 | 983 | _initializeConsumer(): void { 984 | var processedMsgConsumer = (msg: AmqpLib.Message) => { 985 | try { 986 | /* istanbul ignore if */ 987 | if (!msg) { 988 | return; // ignore empty messages (for now) 989 | } 990 | var payload = Queue._unpackMessageContent(msg); 991 | var result = this._consumer(payload); 992 | // convert the result to a promise if it isn't one already 993 | Promise.resolve(result).then((resultValue) => { 994 | // check if there is a reply-to 995 | if (msg.properties.replyTo) { 996 | var options: any = {}; 997 | resultValue = Queue._packMessageContent(resultValue, options); 998 | this._channel.sendToQueue(msg.properties.replyTo, resultValue, options); 999 | } 1000 | // 'hack' added to allow better manual ack control by client (less elegant, but should work) 1001 | if (this._consumerOptions.manualAck !== true && this._consumerOptions.noAck !== true) { 1002 | this._channel.ack(msg); 1003 | } 1004 | }).catch((error) => { 1005 | log.error("Queue.onMessage RPC promise returned error: " + error.message, { module: "amqp-ts", error }); 1006 | }); 1007 | } catch (error) { 1008 | /* istanbul ignore next */ 1009 | log.error("Queue.onMessage consumer function returned error: " + error.message, { module: "amqp-ts", error }); 1010 | } 1011 | }; 1012 | 1013 | var rawMsgConsumer = (msg: AmqpLib.Message) => { 1014 | try { 1015 | this._consumer(msg, this._channel); 1016 | } catch (error) { 1017 | /* istanbul ignore next */ 1018 | log.error("Queue.onMessage consumer function returned error: " + error.message, { module: "amqp-ts", error }); 1019 | } 1020 | }; 1021 | 1022 | var activateConsumerWrapper = (msg: AmqpLib.Message) => { 1023 | if (!msg) { 1024 | /* basic.cancel is triggered on queue deletion if connection is not closed, it will dispatch an empty msg. 1025 | * Throw an exception so that the system is notified. 1026 | * See https://github.com/amqp-node/amqplib/blob/v0.10.3/lib/channel.js#L495-L499 1027 | * https://www.rabbitmq.com/consumer-cancel.html 1028 | */ 1029 | log.warn("activateConsumerWrapper - Message is null. Channel has been canceled."); 1030 | throw new Error("activateConsumerWrapper - Message is null. Channel has been canceled."); 1031 | } 1032 | 1033 | try { 1034 | var message = new Message(msg.content, msg.properties); 1035 | message.fields = msg.fields; 1036 | message._message = msg; 1037 | message._channel = this._channel; 1038 | var result = this._consumer(message); 1039 | // convert the result to a promise if it isn't one already 1040 | Promise.resolve(result).then((resultValue) => { 1041 | // check if there is a reply-to 1042 | if (msg.properties.replyTo) { 1043 | if (!(resultValue instanceof Message)) { 1044 | resultValue = new Message(resultValue, {}); 1045 | } 1046 | resultValue.properties.correlationId = msg.properties.correlationId; 1047 | this._channel.sendToQueue(msg.properties.replyTo, resultValue.content, resultValue.properties); 1048 | } 1049 | }).catch((error) => { 1050 | log.error("Queue.onMessage RPC promise returned error: " + error.message, { module: "amqp-ts", error }); 1051 | }); 1052 | } catch (error) { 1053 | /* istanbul ignore next */ 1054 | log.error("Queue.onMessage consumer function returned error: " + error.message, { module: "amqp-ts", error }); 1055 | } 1056 | }; 1057 | 1058 | this._consumerInitialized = new Promise((resolve, reject) => { 1059 | this.initialized.then(() => { 1060 | var consumerFunction = activateConsumerWrapper; 1061 | if (this._isStartConsumer) { 1062 | consumerFunction = this._rawConsumer ? rawMsgConsumer : processedMsgConsumer; 1063 | } 1064 | this._channel.consume(this._name, consumerFunction, this._consumerOptions, (err, ok) => { 1065 | /* istanbul ignore if */ 1066 | if (err) { 1067 | reject(err); 1068 | } else { 1069 | this._consumerTag = ok.consumerTag; 1070 | resolve(ok); 1071 | } 1072 | }); 1073 | }); 1074 | }); 1075 | } 1076 | 1077 | stopConsumer(): Promise { 1078 | if (!this._consumerInitialized || this._consumerStopping) { 1079 | return Promise.resolve(); 1080 | } 1081 | this._consumerStopping = true; 1082 | return new Promise((resolve, reject) => { 1083 | this._consumerInitialized.then(() => { 1084 | this._channel.cancel(this._consumerTag, (error, ok) => { 1085 | /* istanbul ignore if */ 1086 | if (error) { 1087 | log.error("Channel failed on cancelling: " + error.message, { module: "amqp-ts", error }); 1088 | reject(error); 1089 | } else { 1090 | log.info("Channel canceled.", { module: "amqp-ts" }); 1091 | delete this._consumerInitialized; 1092 | delete this._consumer; 1093 | delete this._consumerOptions; 1094 | delete this._consumerStopping; 1095 | resolve(null); 1096 | } 1097 | }); 1098 | }); 1099 | }); 1100 | } 1101 | 1102 | delete(): Promise { 1103 | if (this._deleting === undefined) { 1104 | this._deleting = new Promise((resolve, reject) => { 1105 | this.initialized.then(() => { 1106 | return Binding.removeBindingsContaining(this); 1107 | }).then(() => { 1108 | return this.stopConsumer(); 1109 | }).then(() => { 1110 | return this._channel.deleteQueue(this._name, {}, (err, ok) => { 1111 | /* istanbul ignore if */ 1112 | if (err) { 1113 | reject(err); 1114 | } else { 1115 | delete this.initialized; // invalidate queue 1116 | delete this._connection._queues[this._name]; // remove the queue from our administration 1117 | this._channel.close((err) => { 1118 | /* istanbul ignore if */ 1119 | if (err) { 1120 | reject(err); 1121 | } else { 1122 | log.info("Channel closed.", { module: "amqp-ts" }); 1123 | delete this._channel; 1124 | delete this._connection; 1125 | resolve(ok); 1126 | } 1127 | }); 1128 | } 1129 | }); 1130 | }).catch((err) => { 1131 | reject(err); 1132 | }); 1133 | }); 1134 | } 1135 | return this._deleting; 1136 | } 1137 | 1138 | close(): Promise { 1139 | if (this._closing === undefined) { 1140 | this._closing = new Promise((resolve, reject) => { 1141 | this.initialized.then(() => { 1142 | return Binding.removeBindingsContaining(this); 1143 | }).then(() => { 1144 | return this.stopConsumer(); 1145 | }).then(() => { 1146 | delete this.initialized; // invalidate queue 1147 | delete this._connection._queues[this._name]; // remove the queue from our administration 1148 | this._channel.close((err) => { 1149 | /* istanbul ignore if */ 1150 | if (err) { 1151 | reject(err); 1152 | } else { 1153 | log.info("Channel closed.", { module: "amqp-ts" }); 1154 | delete this._channel; 1155 | delete this._connection; 1156 | resolve(null); 1157 | } 1158 | }); 1159 | }).catch((err) => { 1160 | reject(err); 1161 | }); 1162 | }); 1163 | } 1164 | return this._closing; 1165 | } 1166 | 1167 | bind(source: Exchange, pattern = "", args: any = {}): Promise { 1168 | var binding = new Binding(this, source, pattern, args); 1169 | return binding.initialized; 1170 | } 1171 | 1172 | unbind(source: Exchange, pattern = "", args: any = {}): Promise { 1173 | return this._connection._bindings[Binding.id(this, source, pattern)].delete(); 1174 | } 1175 | } 1176 | export namespace Queue { 1177 | "use strict"; 1178 | export interface DeclarationOptions { 1179 | exclusive?: boolean; 1180 | durable?: boolean; 1181 | autoDelete?: boolean; 1182 | arguments?: any; 1183 | messageTtl?: number; 1184 | expires?: number; 1185 | deadLetterExchange?: string; 1186 | maxLength?: number; 1187 | prefetch?: number; 1188 | noCreate?: boolean; 1189 | } 1190 | export interface StartConsumerOptions { 1191 | rawMessage?: boolean; 1192 | consumerTag?: string; 1193 | noLocal?: boolean; 1194 | noAck?: boolean; 1195 | manualAck?: boolean; 1196 | exclusive?: boolean; 1197 | priority?: number; 1198 | arguments?: Object; 1199 | } 1200 | export interface ActivateConsumerOptions { 1201 | consumerTag?: string; 1202 | noLocal?: boolean; 1203 | noAck?: boolean; 1204 | manualAck?: boolean; 1205 | exclusive?: boolean; 1206 | priority?: number; 1207 | arguments?: Object; 1208 | } 1209 | export interface StartConsumerResult { 1210 | consumerTag: string; 1211 | } 1212 | export interface InitializeResult { 1213 | queue: string; 1214 | messageCount: number; 1215 | consumerCount: number; 1216 | } 1217 | export interface DeleteResult { 1218 | messageCount: number; 1219 | } 1220 | } 1221 | 1222 | 1223 | //---------------------------------------------------------------------------------------------------- 1224 | // Binding class 1225 | //---------------------------------------------------------------------------------------------------- 1226 | export class Binding { 1227 | initialized: Promise; 1228 | 1229 | _source: Exchange; 1230 | _destination: Exchange | Queue; 1231 | _pattern: string; 1232 | _args: any; 1233 | 1234 | constructor(destination: Exchange | Queue, source: Exchange, pattern = "", args: any = {}) { 1235 | this._source = source; 1236 | this._destination = destination; 1237 | this._pattern = pattern; 1238 | this._args = args; 1239 | this._destination._connection._bindings[Binding.id(this._destination, this._source, this._pattern)] = this; 1240 | this._initialize(); 1241 | } 1242 | 1243 | _initialize(): void { 1244 | this.initialized = new Promise((resolve, reject) => { 1245 | if (this._destination instanceof Queue) { 1246 | var queue = this._destination; 1247 | queue.initialized.then(() => { 1248 | queue._channel.bindQueue(this._destination._name, this._source._name, this._pattern, this._args, (error, ok) => { 1249 | /* istanbul ignore if */ 1250 | if (error) { 1251 | log.error("Failed to create queue binding (" + 1252 | this._source._name + "->" + this._destination._name + ")", 1253 | { module: "amqp-ts", error }); 1254 | delete this._destination._connection._bindings[Binding.id(this._destination, this._source, this._pattern)]; 1255 | reject(error); 1256 | } else { 1257 | resolve(this); 1258 | } 1259 | }); 1260 | }); 1261 | } else { 1262 | var exchange = this._destination; 1263 | exchange.initialized.then(() => { 1264 | exchange._channel.bindExchange(this._destination._name, this._source._name, this._pattern, this._args, (error, ok) => { 1265 | /* istanbul ignore if */ 1266 | if (error) { 1267 | log.error("Failed to create exchange binding (" + 1268 | this._source._name + "->" + this._destination._name + ")", 1269 | { module: "amqp-ts", error }); 1270 | delete this._destination._connection._bindings[Binding.id(this._destination, this._source, this._pattern)]; 1271 | reject(error); 1272 | } else { 1273 | resolve(this); 1274 | } 1275 | }); 1276 | }); 1277 | } 1278 | }); 1279 | } 1280 | 1281 | delete(): Promise { 1282 | return new Promise((resolve, reject) => { 1283 | if (this._destination instanceof Queue) { 1284 | var queue = this._destination; 1285 | queue.initialized.then(() => { 1286 | queue._channel.unbindQueue(this._destination._name, this._source._name, this._pattern, this._args, (err, ok) => { 1287 | /* istanbul ignore if */ 1288 | if (err) { 1289 | reject(err); 1290 | } else { 1291 | delete this._destination._connection._bindings[Binding.id(this._destination, this._source, this._pattern)]; 1292 | resolve(null); 1293 | } 1294 | }); 1295 | }); 1296 | } else { 1297 | var exchange = this._destination; 1298 | exchange.initialized.then(() => { 1299 | exchange._channel.unbindExchange(this._destination._name, this._source._name, this._pattern, this._args, (err, ok) => { 1300 | /* istanbul ignore if */ 1301 | if (err) { 1302 | reject(err); 1303 | } else { 1304 | delete this._destination._connection._bindings[Binding.id(this._destination, this._source, this._pattern)]; 1305 | resolve(null); 1306 | } 1307 | }); 1308 | }); 1309 | } 1310 | }); 1311 | } 1312 | 1313 | static id(destination: Exchange | Queue, source: Exchange, pattern?: string): string { 1314 | pattern = pattern || ""; 1315 | return "[" + source._name + "]to" + (destination instanceof Queue ? "Queue" : "Exchange") + "[" + destination._name + "]" + pattern; 1316 | } 1317 | 1318 | static removeBindingsContaining(connectionPoint: Exchange | Queue): Promise { 1319 | var connection = connectionPoint._connection; 1320 | var promises: Promise[] = []; 1321 | for (var bindingId in connection._bindings) { 1322 | 1323 | var binding: Binding = connection._bindings[bindingId]; 1324 | if (binding._source === connectionPoint || binding._destination === connectionPoint) { 1325 | promises.push(binding.delete()); 1326 | } 1327 | } 1328 | return Promise.all(promises); 1329 | } 1330 | } 1331 | --------------------------------------------------------------------------------