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