├── .editorconfig ├── .gitignore ├── .nvmrc ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── _docs └── redis_data_structure.md ├── _src └── index.ts ├── coffeelint.json ├── examples └── simple-queue.js ├── index.d.ts ├── index.js ├── package-lock.json ├── package.json ├── test ├── mocha.opts ├── test.coffee └── test.js ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = false 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.json] 15 | indent_style = space 16 | indent_size = 2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | dump.rdb 4 | *.profile 5 | *.lock 6 | *.conflict 7 | *.DS_Store 8 | 9 | .project 10 | .settings 11 | .idea 12 | 13 | *.sublime* -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 14.17.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | services: 3 | - redis-server 4 | node_js: 5 | - "12" 6 | - "14" 7 | - "16" 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG for rsmq 2 | 3 | ## 0.12.4 4 | 5 | * Added Travis tests for Node 16 6 | * Updated dependencies 7 | * Removed Travis tests for Node 10 8 | ## 0.12.3 9 | 10 | * Added .NET implementation to README.md (#126) 11 | * Fixed index.d.ts file to #110. (#123) 12 | * Added rsmq-promise-native to README.md (#122) 13 | * Removed support for Node 8 14 | * Updated dependencies 15 | ## 0.12.2 16 | 17 | * Added callback to `quit` method (#110) 18 | 19 | ## 0.12.1 20 | 21 | * Added Travis tests for Node 14 22 | 23 | ## 0.12.0 24 | 25 | * Removed Travis tests for Node 6 26 | * Updated dependencies 27 | * Added README for RSMQ Rust port. Thanks @DavidBM. (#107) 28 | 29 | ## 0.11.0 30 | 31 | * Refactored all code from coffeescript to typescript. 32 | * Added tests for #90 33 | * Removed support for Node 4 34 | * Support for `password` option when connecting to Redis (#70) 35 | 36 | ## 0.10.0 37 | 38 | * added optional Promise support and tests 39 | 40 | ## 0.9.3 41 | 42 | * removed `hiredis` optionalDependency 43 | 44 | ## 0.9.2 45 | 46 | * add realtime option in typescript typings 47 | 48 | ## 0.9.0 49 | 50 | * Added realtime option for Redis PUB/SUB when a new message is sent to RSMQ 51 | * Added tests for realtime 52 | * Added Travis tests for Node 10 53 | * Updated dependencies (lodash and dev.async) 54 | 55 | ## 0.8.4 56 | 57 | * Added Typescript typings 58 | 59 | ## 0.8.3 60 | 61 | * Removed Travis test for Node 7 62 | * Added Travis test for Node 8 63 | * Mocha 4.x fixes for Travis test runner. 64 | 65 | ## 0.8.2 66 | 67 | * Added mention for "RSMQ in other languages" in README.md 68 | 69 | ## 0.8.1 70 | 71 | * Make sure `setQueueAttributes` does refresh `modified` field. Fix for #47. Thanks @igr 72 | 73 | ## 0.8.0 74 | 75 | * Allow unlimited message size with `maxsize=-1` option. 76 | 77 | ## 0.7.2 78 | 79 | * Removed Travis tests for Node 0.12.x 80 | 81 | ## 0.7.1 82 | 83 | * Fix #30 Increased queue name limit from 80 to 160 chars. 84 | * Run Travis tests latest Node 6.x, 4.x 85 | 86 | ## 0.7.0 87 | 88 | * Updated dependencies 89 | 90 | ## 0.6.0 91 | 92 | * Added `popMessage` method 93 | * Fix #23: Use of external Redis instance 94 | * Added Tests for `popMessage` 95 | * Use current version of lodash (4.5.1) and redis (2.4.2) 96 | 97 | ## 0.4.0 98 | 99 | * Updated `redis` / `hiredis` modules. 100 | * Node 0.8.x is no longer supported. 101 | * Removed Travis tests for iojs 102 | * Travis tests for Node 4.1 and 5.0 103 | 104 | ## 0.3.16 105 | 106 | * Docs (Redis 2.6+ version requirement) 107 | 108 | ## 0.3.15 109 | 110 | * Added LICENSE.md 111 | * Docs (added Links to modules) 112 | 113 | ## 0.3.14 114 | 115 | * Fix `changeMessageVisibility` syntax fix. Failed if this method will be called as first call. 116 | 117 | ## 0.3.13 118 | 119 | * Fix `hiddenmsgs` display in `getQueueAttributes` 120 | 121 | ## 0.3.12 122 | 123 | * Added `quit` method 124 | 125 | ## 0.3.11 126 | 127 | * Docs 128 | 129 | ## 0.3.10 130 | 131 | * Docs 132 | 133 | ## 0.3.9 134 | 135 | * Added logo to README.md 136 | 137 | ## 0.3.8 138 | 139 | * implemented `setQueueAttributes` 140 | * switched from underscore to lodash 141 | * added Travis test for Node.js 0.11 142 | * updated the docs 143 | * added tests for `setQueueAttributes` 144 | 145 | ## 0.3.5 146 | 147 | * Make `hiredis` optional. 148 | 149 | ## 0.3.4 150 | 151 | * Added support for [https://github.com/mranney/node_redis#rediscreateclientport-host-options](redis.createClient) `options` object. 152 | 153 | ## 0.3.3 154 | 155 | * docs 156 | 157 | ## 0.3.2 158 | 159 | * Added constructor option `client` to reuse existing redis clients 160 | 161 | ## 0.3.1 162 | 163 | * Add details to README.md for constructor object. #3 164 | 165 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2019 Patrick Liess http://www.tcs.de 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![RSMQ: Redis Simple Message Queue for Node.js](https://img.webmart.de/rsmq_wide.png) 2 | 3 | # Redis Simple Message Queue 4 | A lightweight message queue for Node.js that requires no dedicated queue server. Just a Redis server. 5 | 6 | [![Build Status](https://secure.travis-ci.org/smrchy/rsmq.png?branch=master)](http://travis-ci.org/smrchy/rsmq) 7 | [![Dependency Status](https://david-dm.org/smrchy/rsmq.svg)](https://david-dm.org/smrchy/rsmq) 8 | 9 | **tl;dr:** If you run a Redis server and currently use Amazon SQS or a similar message queue you might as well use this fast little replacement. Using a shared Redis server multiple Node.js processes can send / receive messages. 10 | 11 | ## Features 12 | * Lightweight: **Just Redis** and ~500 lines of javascript. 13 | * Speed: Send/receive 10000+ messages per second on an average machine. It's **just Redis**. 14 | * Guaranteed **delivery of a message to exactly one recipient** within a messages visibility timeout. 15 | * Received messages that are not deleted will reappear after the visibility timeout. 16 | * [Test coverage](http://travis-ci.org/smrchy/rsmq) 17 | * A message is deleted by the message id. The message id is returned by the `sendMessage` and `receiveMessage` method. 18 | * Messages stay in the queue unless deleted. 19 | * Optional RESTful interface via [rest-rsmq](https://github.com/smrchy/rest-rsmq) 20 | * Typescript Typings ❤️ 21 | * Optional Promise-based API (only if `Promise` is defined), just suffix your method with `Async`, eg: `sendMessage` -> `sendMessageAsync`, all queue methods are supported 22 | 23 | **Note:** RSMQ uses the Redis EVAL command (LUA scripts) so the minimum Redis version is 2.6+. 24 | 25 | ## Usage 26 | * After creating a queue you can send messages to that queue. 27 | * The messages will be handled in a **FIFO** (first in first out) manner unless specified with a delay. 28 | * Every message has a unique `id` that you can use to delete the message. 29 | * The `sendMessage` method will return the `id` for a sent message. 30 | * The `receiveMessage` method will return an `id` along with the message and some stats. 31 | * Should you not delete the message it will be eligible to be received again after the visibility timeout is reached. 32 | * Please have a look at the `createQueue` and `receiveMessage` methods described below for optional parameters like **visibility timeout** and **delay**. 33 | 34 | ## Installation 35 | `npm install rsmq` 36 | 37 | ## Modules for RSMQ 38 | To keep the core of *RSMQ* small additional functionality is available as modules: 39 | 40 | * [**rsmq-worker**](https://github.com/mpneuried/rsmq-worker) Helper to implement a worker with RSMQ. 41 | * [**rest-rsmq**](https://github.com/smrchy/rest-rsmq) A RESTful interface for RSMQ. 42 | * [**rsmq-cli**](https://github.com/mpneuried/rsmq-cli) A command-line interface / Terminal client for RSMQ. 43 | * [**rsmq-promise-native**](https://www.npmjs.com/package/rsmq-promise-native) Native promise interface for RSMQ. 44 | * [**rsmq-promise**](https://www.npmjs.com/package/rsmq-promise) Promise interface for RSMQ (via Bluebird). 45 | 46 | ## RSMQ in other languages 47 | 48 | The simplicity of RSMQ is useful in other languages. Here is a list of implementations in other languages: 49 | 50 | * [**Rust**](https://github.com/Couragium/rsmq-async-rs) RSMQ Async for Rust. See [#107](https://github.com/smrchy/rsmq/issues/107) 51 | * [**Java**](https://github.com/igr/jrsmq) RSMQ for Java. See [#48](https://github.com/smrchy/rsmq/issues/48) 52 | * [**PHP**](https://github.com/michsindelar/PhpRSMQ) RSMQ for PHP (work in progress) 53 | * [**CSharp/DotNet**](https://github.com/tontonrally/rsmqCsharp) RSMQ for .NET 54 | 55 | Note: Should you plan to port RSQM to another language please make sure to have tests to ensure compatibility with all RSMQ clients. And of course: let me know so i can mention your port here. 56 | 57 | ## Methods 58 | 59 | ### Constructor 60 | Creates a new instance of RSMQ. 61 | 62 | Parameters: 63 | 64 | * `host` (String): *optional (Default: "127.0.0.1")* The Redis server 65 | * `port` (Number): *optional (Default: 6379)* The Redis port 66 | * `options` (Object): *optional (Default: {})* The [Redis options](https://github.com/NodeRedis/node_redis#options-object-properties) object. 67 | * `client` (RedisClient): *optional* A existing redis client instance. `host` and `server` will be ignored. 68 | * `ns` (String): *optional (Default: "rsmq")* The namespace prefix used for all keys created by RSMQ 69 | * `realtime` (Boolean): *optional (Default: false)* Enable realtime PUBLISH of new messages (see the [Realtime section](#realtime)) 70 | * `password` (String): *optional (Default: null)* If your Redis server requires a password supply it here 71 | 72 | Example: 73 | 74 | ```javascript 75 | const RedisSMQ = require("rsmq"); 76 | const rsmq = new RedisSMQ( {host: "127.0.0.1", port: 6379, ns: "rsmq"} ); 77 | ``` 78 | 79 | ### Queue 80 | 81 | #### createQueue(options, callback) 82 | Create a new queue. 83 | 84 | Parameters: 85 | 86 | * `qname` (String): The Queue name. Maximum 160 characters; alphanumeric characters, hyphens (-), and underscores (_) are allowed. 87 | * `vt` (Number): *optional* *(Default: 30)* The length of time, in seconds, that a message received from a queue will be invisible to other receiving components when they ask to receive messages. Allowed values: 0-9999999 (around 115 days) 88 | * `delay` (Number): *optional* *(Default: 0)* The time in seconds that the delivery of all new messages in the queue will be delayed. Allowed values: 0-9999999 (around 115 days) 89 | * `maxsize` (Number): *optional* *(Default: 65536)* The maximum message size in bytes. Allowed values: 1024-65536 and -1 (for unlimited size) 90 | 91 | Returns: 92 | 93 | * `1` (Number) 94 | 95 | Example: 96 | 97 | ```javascript 98 | rsmq.createQueue({ qname: "myqueue" }, function (err, resp) { 99 | if (err) { 100 | console.error(err) 101 | return 102 | } 103 | 104 | if (resp === 1) { 105 | console.log("queue created") 106 | } 107 | }); 108 | ``` 109 | 110 | #### listQueues(options, callback) 111 | List all queues 112 | 113 | Returns an array: 114 | 115 | * `["qname1", "qname2"]` 116 | 117 | Example: 118 | 119 | ```javascript 120 | rsmq.listQueues(function (err, queues) { 121 | if (err) { 122 | console.error(err) 123 | return 124 | } 125 | 126 | console.log("Active queues: " + queues.join( "," ) ) 127 | }); 128 | ``` 129 | 130 | #### deleteQueue(options, callback) 131 | Deletes a queue and all messages. 132 | 133 | Parameters: 134 | 135 | * `qname` (String): The Queue name. 136 | 137 | Returns: 138 | 139 | * `1` (Number) 140 | 141 | Example: 142 | 143 | ```javascript 144 | rsmq.deleteQueue({ qname: "myqueue" }, function (err, resp) { 145 | if (err) { 146 | console.error(err) 147 | return 148 | } 149 | 150 | if (resp === 1) { 151 | console.log("Queue and all messages deleted.") 152 | } else { 153 | console.log("Queue not found.") 154 | } 155 | }); 156 | ``` 157 | 158 | #### getQueueAttributes(options, callback) 159 | Get queue attributes, counter and stats 160 | 161 | Parameters: 162 | 163 | * `qname` (String): The Queue name. 164 | 165 | Returns an object: 166 | 167 | * `vt` (Number): The visibility timeout for the queue in seconds 168 | * `delay` (Number): The delay for new messages in seconds 169 | * `maxsize` (Number): The maximum size of a message in bytes 170 | * `totalrecv` (Number): Total number of messages received from the queue 171 | * `totalsent` (Number): Total number of messages sent to the queue 172 | * `created` (Number): Timestamp (epoch in seconds) when the queue was created 173 | * `modified` (Number): Timestamp (epoch in seconds) when the queue was last modified with `setQueueAttributes` 174 | * `msgs` (Number): Current number of messages in the queue 175 | * `hiddenmsgs` (Number): Current number of hidden / not visible messages. A message can be hidden while "in flight" due to a `vt` parameter or when sent with a `delay` 176 | 177 | Example: 178 | 179 | ```javascript 180 | rsmq.getQueueAttributes({ qname: "myqueue" }, function (err, resp) { 181 | if (err) { 182 | console.error(err); 183 | return; 184 | } 185 | 186 | console.log("=============================================="); 187 | console.log("=================Queue Stats=================="); 188 | console.log("=============================================="); 189 | console.log("visibility timeout: ", resp.vt); 190 | console.log("delay for new messages: ", resp.delay); 191 | console.log("max size in bytes: ", resp.maxsize); 192 | console.log("total received messages: ", resp.totalrecv); 193 | console.log("total sent messages: ", resp.totalsent); 194 | console.log("created: ", resp.created); 195 | console.log("last modified: ", resp.modified); 196 | console.log("current n of messages: ", resp.msgs); 197 | console.log("hidden messages: ", resp.hiddenmsgs); 198 | }); 199 | ``` 200 | 201 | 202 | #### setQueueAttributes(options, callback) 203 | Sets queue parameters. 204 | 205 | Parameters: 206 | 207 | * `qname` (String): The Queue name. 208 | * `vt` (Number): *optional* * The length of time, in seconds, that a message received from a queue will be invisible to other receiving components when they ask to receive messages. Allowed values: 0-9999999 (around 115 days) 209 | * `delay` (Number): *optional* The time in seconds that the delivery of all new messages in the queue will be delayed. Allowed values: 0-9999999 (around 115 days) 210 | * `maxsize` (Number): *optional* The maximum message size in bytes. Allowed values: 1024-65536 and -1 (for unlimited size) 211 | 212 | Note: At least one attribute (vt, delay, maxsize) must be supplied. Only attributes that are supplied will be modified. 213 | 214 | Returns an object: 215 | 216 | * `vt` (Number): The visibility timeout for the queue in seconds 217 | * `delay` (Number): The delay for new messages in seconds 218 | * `maxsize` (Number): The maximum size of a message in bytes 219 | * `totalrecv` (Number): Total number of messages received from the queue 220 | * `totalsent` (Number): Total number of messages sent to the queue 221 | * `created` (Number): Timestamp (epoch in seconds) when the queue was created 222 | * `modified` (Number): Timestamp (epoch in seconds) when the queue was last modified with `setQueueAttributes` 223 | * `msgs` (Number): Current number of messages in the queue 224 | * `hiddenmsgs` (Number): Current number of hidden / not visible messages. A message can be hidden while "in flight" due to a `vt` parameter or when sent with a `delay` 225 | 226 | Example: 227 | 228 | ```javascript 229 | rsmq.setQueueAttributes({ qname: "myqueue", vt: "30"}, function (err, resp) { 230 | if (err) { 231 | console.error(err) 232 | return 233 | } 234 | 235 | console.log("changed the invisibility time of messages that have been received to 30 seconds"); 236 | console.log(resp); 237 | }); 238 | ``` 239 | 240 | ### Messages 241 | 242 | #### sendMessage 243 | Sends a new message. 244 | 245 | Parameters: 246 | 247 | * `qname` (String) 248 | * `message` (String) 249 | * `delay` (Number): *optional* *(Default: queue settings)* The time in seconds that the delivery of the message will be delayed. Allowed values: 0-9999999 (around 115 days) 250 | 251 | Returns: 252 | 253 | * `id` (String): The internal message id. 254 | 255 | Example: 256 | 257 | ```javascript 258 | rsmq.sendMessage({ qname: "myqueue", message: "Hello World "}, function (err, resp) { 259 | if (err) { 260 | console.error(err) 261 | return 262 | } 263 | 264 | console.log("Message sent. ID:", resp); 265 | }); 266 | ``` 267 | 268 | #### receiveMessage(options, callback) 269 | Receive the next message from the queue. 270 | 271 | Parameters: 272 | 273 | * `qname` (String): The Queue name. 274 | * `vt` (Number): *optional* *(Default: queue settings)* The length of time, in seconds, that the received message will be invisible to others. Allowed values: 0-9999999 (around 115 days) 275 | 276 | Returns an object: 277 | 278 | * `message` (String): The message's contents. 279 | * `id` (String): The internal message id. 280 | * `sent` (Number): Timestamp of when this message was sent / created. 281 | * `fr` (Number): Timestamp of when this message was first received. 282 | * `rc` (Number): Number of times this message was received. 283 | 284 | Note: Will return an empty object if no message is there 285 | 286 | Example: 287 | 288 | ```javascript 289 | rsmq.receiveMessage({ qname: "myqueue" }, function (err, resp) { 290 | if (err) { 291 | console.error(err) 292 | return 293 | } 294 | 295 | if (resp.id) { 296 | console.log("Message received.", resp) 297 | } else { 298 | console.log("No messages for me...") 299 | } 300 | }); 301 | ``` 302 | 303 | #### deleteMessage(options, callback) 304 | Parameters: 305 | 306 | * `qname` (String): The Queue name. 307 | * `id` (String): message id to delete. 308 | 309 | Returns: 310 | 311 | * `1` if successful, `0` if the message was not found (Number). 312 | 313 | Example: 314 | 315 | ```javascript 316 | rsmq.deleteMessage({ qname: "myqueue", id: "dhoiwpiirm15ce77305a5c3a3b0f230c6e20f09b55" }, function (err, resp) { 317 | if (err) { 318 | console.error(err) 319 | return 320 | } 321 | 322 | if (resp === 1) { 323 | console.log("Message deleted.") 324 | } else { 325 | console.log("Message not found.") 326 | } 327 | }); 328 | ``` 329 | 330 | #### popMessage(options, callback) 331 | Receive the next message from the queue **and delete it**. 332 | 333 | **Important:** This method deletes the message it receives right away. There is no way to receive the message again if something goes wrong while working on the message. 334 | 335 | Parameters: 336 | 337 | * `qname` (String): The Queue name. 338 | 339 | Returns an object: 340 | 341 | * `message` (String): The message's contents. 342 | * `id` (String): The internal message id. 343 | * `sent` (Number): Timestamp of when this message was sent / created. 344 | * `fr` (Number): Timestamp of when this message was first received. 345 | * `rc` (Number): Number of times this message was received. 346 | 347 | Note: Will return an empty object if no message is there 348 | 349 | Example: 350 | 351 | ```javascript 352 | rsmq.popMessage({ qname: "myqueue" }, function (err, resp) { 353 | if (err) { 354 | console.error(err) 355 | return 356 | } 357 | 358 | if (resp.id) { 359 | console.log("Message received and deleted from queue", resp) 360 | } else { 361 | console.log("No messages for me...") 362 | } 363 | }); 364 | ``` 365 | 366 | #### changeMessageVisibility(options, callback) 367 | Change the visibility timer of a single message. 368 | The time when the message will be visible again is calculated from the current time (now) + `vt`. 369 | 370 | Parameters: 371 | 372 | * `qname` (String): The Queue name. 373 | * `id` (String): The message id. 374 | * `vt` (Number): The length of time, in seconds, that this message will not be visible. Allowed values: 0-9999999 (around 115 days) 375 | 376 | Returns: 377 | 378 | * `1` if successful, `0` if the message was not found (Number). 379 | 380 | Example: 381 | 382 | ```javascript 383 | rsmq.changeMessageVisibility({ qname: "myqueue", vt: "60", id: "dhoiwpiirm15ce77305a5c3a3b0f230c6e20f09b55" }, function (err, resp) { 384 | if (err) { 385 | console.error(err) 386 | return 387 | } 388 | 389 | if (resp === 1) { 390 | console.log("message hidden for 60 seconds") 391 | } 392 | }); 393 | ``` 394 | 395 | 396 | ### quit(callback) 397 | Disconnect the redis client. 398 | This is only useful if you are using rsmq within a script and want node to be able to exit. 399 | 400 | ## Realtime 401 | When [initializing](#initialize) RSMQ you can enable the realtime PUBLISH for new messages. On every new message that gets sent to RSQM via `sendMessage` a Redis PUBLISH will be issued to `{rsmq.ns}:rt:{qname}`. 402 | 403 | Example for RSMQ with default settings: 404 | 405 | * The queue `testQueue` already contains 5 messages. 406 | * A new message is being sent to the queue `testQueue`. 407 | * The following Redis command will be issued: `PUBLISH rsmq:rt:testQueue 6` 408 | 409 | ### How to use the realtime option 410 | Besides the PUBLISH when a new message is sent to RSMQ nothing else will happen. Your app could use the Redis SUBSCRIBE command to be notified of new messages and issue a `receiveMessage` then. However make sure not to listen with multiple workers for new messages with SUBSCRIBE to prevent multiple simultaneous `receiveMessage` calls. 411 | 412 | ## Changes 413 | see the [CHANGELOG](https://github.com/smrchy/rsmq/blob/master/CHANGELOG.md) 414 | 415 | ## Other projects 416 | |Name|Description| 417 | |:--|:--| 418 | |[**node-cache**](https://github.com/tcs-de/nodecache)|Simple and fast Node.js internal caching. Node internal in memory cache like memcached.| 419 | |[**redis-tagging**](https://github.com/smrchy/redis-tagging)|A Node.js helper library to make tagging of items in any legacy database (SQL or NoSQL) easy and fast.| 420 | |[**redis-sessions**](https://github.com/smrchy/redis-sessions)|An advanced session store for Node.js and Redis| 421 | |[**rsmq-worker**](https://github.com/mpneuried/rsmq-worker)|Helper to implement a worker based on [RSMQ (Redis Simple Message Queue)](https://github.com/smrchy/rsmq).| 422 | |[**connect-redis-sessions**](https://github.com/mpneuried/connect-redis-sessions)|A connect or express middleware to use [redis sessions](https://github.com/smrchy/redis-sessions) that lets you handle multiple sessions per user_id.| 423 | 424 | ## The MIT License 425 | Please see the LICENSE.md file. 426 | -------------------------------------------------------------------------------- /_docs/redis_data_structure.md: -------------------------------------------------------------------------------- 1 | # Redis Data Structure 2 | 3 | ## rsmq:QUEUES *SET* 4 | 5 | **MEMBER** The queue name 6 | 7 | The global SET, which stores all used queue names. When a queue is created the name is added to this set as a member. When a queue is deleted the member will be removed from this set. 8 | 9 | 10 | ## rsmq:{qname}:Q *HASH* 11 | 12 | This hash keeps all data for a single queue. 13 | 14 | **FIELDS** 15 | * `{msgid}`: The message 16 | * `{msgid}:rc`: The receive counter for a single message. Will be incremented on each receive. 17 | * `{msgid}:fr`: The timestamp when this message was received for the first time. Will be created on the first receive. 18 | * `totalsent`: The total number of messages sent to this queue. 19 | * `totalrecv`: The total number of messages received from this queue. 20 | 21 | ## rsmq:{qname} *ZSET* 22 | 23 | A sorted set of all messages of a single queue 24 | 25 | **SCORE** Next possible receive timestamp (epoch time in ms) 26 | 27 | **MEMBER** The `{msgid}' 28 | -------------------------------------------------------------------------------- /_src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | ____ ____ __ __ ___ 3 | | _ \ / ___| | \/ | / _ \ 4 | | |_) | \___ \ | |\/| | | | | | 5 | | _ < ___) | | | | | | |_| | 6 | |_| \_\ |____/ |_| |_| \__\_\ 7 | 8 | A Really Simple Message Queue based on Redis 9 | 10 | */ 11 | import * as _ from "lodash" 12 | import * as RedisInst from "redis" 13 | const EventEmitter = require("events").EventEmitter 14 | 15 | /* 16 | To create a new instance use: 17 | 18 | RedisSMQ = require("rsmq") 19 | rsmq = new RedisSMQ() 20 | 21 | Paramenters for RedisSMQ: 22 | 23 | * `host` (String): *optional (Default: "127.0.0.1")* The Redis server 24 | * `port` (Number): *optional (Default: 6379)* The Redis port 25 | * `options` (Object): *optional (Default: {})* The Redis options object. 26 | * `client` (RedisClient): *optional* A existing redis client instance. `host` and `server` will be ignored. 27 | * `ns` (String): *optional (Default: "rsmq")* The namespace prefix used for all keys created by **rsmq** 28 | * `realtime` (Boolean): *optional (Default: false)* Enable realtime PUBLISH of new messages 29 | */ 30 | class RedisSMQ extends EventEmitter { 31 | constructor (options: any = {}) { 32 | super(options); 33 | if (Promise) { 34 | // create async versions of methods 35 | _.forEach([ 36 | "changeMessageVisibility", 37 | "createQueue", 38 | "deleteMessage", 39 | "deleteQueue", 40 | "getQueueAttributes", 41 | "listQueues", 42 | "popMessage", 43 | "receiveMessage", 44 | "sendMessage", 45 | "setQueueAttributes", 46 | "quit" 47 | ], this.asyncify); 48 | } 49 | const opts = _.extend({ 50 | host: "127.0.0.1", 51 | port: 6379, 52 | options: { 53 | password: options.password || null 54 | }, 55 | client: null, 56 | ns: "rsmq", 57 | realtime: false 58 | }, options); 59 | opts.options.host = opts.host; 60 | opts.options.port = opts.port; 61 | this.realtime = opts.realtime; 62 | this.redisns = opts.ns + ":"; 63 | 64 | if (opts.client && options.client.constructor.name === "RedisClient") { 65 | this.redis = opts.client 66 | } 67 | else { 68 | this.redis = RedisInst.createClient(opts) 69 | } 70 | 71 | this.connected = this.redis.connected || false; 72 | 73 | // If external client is used it might alrdy be connected. So we check here: 74 | if (this.connected) { 75 | this.emit("connect"); 76 | this.initScript(); 77 | } 78 | // Once the connection is up 79 | this.redis.on("connect", () => { 80 | this.connected = true; 81 | this.emit("connect"); 82 | this.initScript(); 83 | }); 84 | 85 | this.redis.on("error", (err) => { 86 | if (err.message.indexOf( "ECONNREFUSED" )) { 87 | this.connected = false; 88 | this.emit("disconnect"); 89 | } 90 | else { 91 | console.error( "Redis ERROR", err ) 92 | this.emit( "error" ) 93 | } 94 | }); 95 | this._initErrors(); 96 | } 97 | 98 | // helper to create async versions of our public methods (ie. methods that return promises) 99 | // example: getQueue -> getQueueAsync 100 | private asyncify = (methodKey) => { 101 | const asyncMethodKey = methodKey + "Async"; 102 | this[asyncMethodKey] = (...args) => { 103 | return new Promise((resolve, reject) => { 104 | this[methodKey](...args, (err, result) => { 105 | if (err) { 106 | reject(err); 107 | return; 108 | } 109 | resolve(result); 110 | }); 111 | }); 112 | } 113 | } 114 | 115 | // kill the connection of the redis client, so your node script will be able to exit. 116 | public quit = (cb) => { 117 | if (cb === undefined) { 118 | cb = () => {} 119 | } 120 | this.redis.quit(cb); 121 | } 122 | 123 | private _getQueue = (qname, uid, cb) => { 124 | const mc = [ 125 | ["hmget", `${this.redisns}${qname}:Q`, "vt", "delay", "maxsize"], 126 | ["time"] 127 | ]; 128 | this.redis.multi(mc).exec( (err, resp) => { 129 | if (err) { this._handleError(cb, err); return; } 130 | if (resp[0][0] === null || resp[0][1] === null || resp[0][2] === null) { 131 | this._handleError(cb, "queueNotFound"); 132 | return; 133 | } 134 | // Make sure to always have correct 6digit millionth seconds from redis 135 | const ms: any = this._formatZeroPad(Number(resp[1][1]), 6); 136 | // Create the epoch time in ms from the redis timestamp 137 | const ts = Number(resp[1][0] + ms.toString(10).slice(0, 3)); 138 | 139 | const q: any = { 140 | vt: parseInt(resp[0][0], 10), 141 | delay: parseInt(resp[0][1], 10), 142 | maxsize: parseInt(resp[0][2], 10), 143 | ts: ts 144 | }; 145 | 146 | // Need to create a uniqueid based on the redis timestamp, 147 | // the queue name and a random number. 148 | // The first part is the redis time.toString(36) which 149 | // lets redis order the messages correctly even when they are 150 | // in the same millisecond. 151 | if (uid) { 152 | uid = this._makeid(22); 153 | q.uid = Number(resp[1][0] + ms).toString(36) + uid; 154 | } 155 | cb(null, q); 156 | }); 157 | } 158 | 159 | // This must be done via LUA script 160 | // We can only set a visibility of a message if it really exists. 161 | public changeMessageVisibility = (options, cb) => { 162 | if (this._validate(options, ["qname","id","vt"], cb) === false) 163 | return; 164 | 165 | this._getQueue(options.qname, false, (err, q) => { 166 | if (err) { 167 | this._handleError(cb, err); 168 | return; 169 | } 170 | // Make really sure that the LUA script is loaded 171 | if (this.changeMessageVisibility_sha1) { 172 | this._changeMessageVisibility(options, q, cb) 173 | return 174 | } 175 | this.on("scriptload:changeMessageVisibility", () => { 176 | this._changeMessageVisibility(options, q, cb); 177 | }); 178 | }); 179 | } 180 | 181 | private _changeMessageVisibility = (options, q, cb) => { 182 | this.redis.evalsha(this.changeMessageVisibility_sha1, 3, `${this.redisns}${options.qname}`, options.id, q.ts + options.vt * 1000, (err, resp) => { 183 | if (err) { 184 | this._handleError(cb, err); 185 | return; 186 | } 187 | cb(null, resp); 188 | }); 189 | } 190 | 191 | public createQueue = (options, cb) => { 192 | const key = `${this.redisns}${options.qname}:Q`; 193 | options.vt = options.vt != null ? options.vt : 30; 194 | options.delay = options.delay != null ? options.delay : 0; 195 | options.maxsize = options.maxsize != null ? options.maxsize : 65536; 196 | if (this._validate(options, ["qname", "vt", "delay", "maxsize"], cb) === false) 197 | return; 198 | 199 | this.redis.time( (err, resp) => { 200 | if (err) { 201 | this._handleError(cb, err); 202 | return 203 | } 204 | 205 | const mc = [ 206 | ["hsetnx", key, "vt", options.vt], 207 | ["hsetnx", key, "delay", options.delay], 208 | ["hsetnx", key, "maxsize", options.maxsize], 209 | ["hsetnx", key, "created", resp[0]], 210 | ["hsetnx", key, "modified", resp[0]], 211 | ]; 212 | 213 | this.redis.multi(mc).exec( (err, resp) => { 214 | if (err) { 215 | this._handleError(cb, err); 216 | return 217 | } 218 | if (resp[0] === 0) { 219 | this._handleError(cb, "queueExists"); 220 | return; 221 | } 222 | // We created a new queue 223 | // Also store it in the global set to keep an index of all queues 224 | this.redis.sadd(`${this.redisns}QUEUES`, options.qname, (err, resp) => { 225 | if (err) { 226 | this._handleError(cb, err); 227 | return; 228 | } 229 | cb(null, 1); 230 | }); 231 | }); 232 | }); 233 | } 234 | 235 | public deleteMessage = (options, cb) => { 236 | if (this._validate(options, ["qname","id"],cb) === false) 237 | return; 238 | const key = `${this.redisns}${options.qname}`; 239 | const mc = [ 240 | ["zrem", key, options.id], 241 | ["hdel", `${key}:Q`, `${options.id}`, `${options.id}:rc`, `${options.id}:fr`] 242 | ]; 243 | 244 | this.redis.multi(mc).exec( (err, resp) => { 245 | if (err) { this._handleError(cb, err); return; } 246 | if (resp[0] === 1 && resp[1] > 0) { 247 | cb(null, 1) 248 | } 249 | else { 250 | cb(null, 0) 251 | } 252 | }); 253 | } 254 | 255 | public deleteQueue = (options, cb) => { 256 | if (this._validate(options, ["qname"],cb) === false) 257 | return; 258 | 259 | const key = `${this.redisns}${options.qname}`; 260 | const mc = [ 261 | ["del", `${key}:Q`, key], // The queue hash and messages zset 262 | ["srem", `${this.redisns}QUEUES`, options.qname] 263 | ]; 264 | 265 | this.redis.multi(mc).exec( (err,resp) => { 266 | if (err) { this._handleError(cb, err); return; } 267 | if (resp[0] === 0) { 268 | this._handleError(cb, "queueNotFound"); 269 | return; 270 | } 271 | cb(null, 1); 272 | }); 273 | } 274 | 275 | public getQueueAttributes = (options, cb) => { 276 | if (this._validate(options, ["qname"],cb) === false) 277 | return; 278 | 279 | const key = `${this.redisns}${options.qname}`; 280 | this.redis.time( (err, resp) => { 281 | if (err) { this._handleError(cb, err); return; } 282 | 283 | // Get basic attributes and counter 284 | // Get total number of messages 285 | // Get total number of messages in flight (not visible yet) 286 | const mc = [ 287 | ["hmget", `${key}:Q`, "vt", "delay", "maxsize", "totalrecv", "totalsent", "created", "modified"], 288 | ["zcard", key], 289 | ["zcount", key, resp[0] + "000", "+inf"] 290 | ]; 291 | this.redis.multi(mc).exec( (err, resp) => { 292 | if (err) { 293 | this._handleError(cb, err); 294 | return; 295 | } 296 | 297 | if (resp[0][0] === null) { 298 | this._handleError(cb, "queueNotFound") 299 | return 300 | } 301 | const o = { 302 | vt: parseInt(resp[0][0], 10), 303 | delay: parseInt(resp[0][1], 10), 304 | maxsize: parseInt(resp[0][2], 10), 305 | totalrecv: parseInt(resp[0][3], 10) || 0, 306 | totalsent: parseInt(resp[0][4], 10) || 0, 307 | created: parseInt(resp[0][5], 10), 308 | modified: parseInt(resp[0][6], 10), 309 | msgs: resp[1], 310 | hiddenmsgs: resp[2] 311 | }; 312 | cb(null, o); 313 | }); 314 | }); 315 | } 316 | 317 | private _handleReceivedMessage = (cb) => { 318 | return (err, resp) => { 319 | if (err) { this._handleError(cb, err); return; } 320 | if (!resp.length) { 321 | cb(null, {}); 322 | return; 323 | } 324 | const o = { 325 | id: resp[0], 326 | message: resp[1], 327 | rc: resp[2], 328 | fr: Number(resp[3]), 329 | sent: Number(parseInt(resp[0].slice(0, 10), 36) / 1000) 330 | } 331 | cb(null, o); 332 | } 333 | } 334 | 335 | private initScript = () => { 336 | // The popMessage LUA Script 337 | // 338 | // Parameters: 339 | // 340 | // KEYS[1]: the zset key 341 | // KEYS[2]: the current time in ms 342 | // 343 | // * Find a message id 344 | // * Get the message 345 | // * Increase the rc (receive count) 346 | // * Use hset to set the fr (first receive) time 347 | // * Return the message and the counters 348 | // 349 | // Returns: 350 | // 351 | // {id, message, rc, fr} 352 | 353 | const script_popMessage = `local msg = redis.call("ZRANGEBYSCORE", KEYS[1], "-inf", KEYS[2], "LIMIT", "0", "1") 354 | if #msg == 0 then 355 | return {} 356 | end 357 | redis.call("HINCRBY", KEYS[1] .. ":Q", "totalrecv", 1) 358 | local mbody = redis.call("HGET", KEYS[1] .. ":Q", msg[1]) 359 | local rc = redis.call("HINCRBY", KEYS[1] .. ":Q", msg[1] .. ":rc", 1) 360 | local o = {msg[1], mbody, rc} 361 | if rc==1 then 362 | table.insert(o, KEYS[2]) 363 | else 364 | local fr = redis.call("HGET", KEYS[1] .. ":Q", msg[1] .. ":fr") 365 | table.insert(o, fr) 366 | end 367 | redis.call("ZREM", KEYS[1], msg[1]) 368 | redis.call("HDEL", KEYS[1] .. ":Q", msg[1], msg[1] .. ":rc", msg[1] .. ":fr") 369 | return o` 370 | 371 | // The receiveMessage LUA Script 372 | // 373 | // Parameters: 374 | // 375 | // KEYS[1]: the zset key 376 | // KEYS[2]: the current time in ms 377 | // KEYS[3]: the new calculated time when the vt runs out 378 | // 379 | // * Find a message id 380 | // * Get the message 381 | // * Increase the rc (receive count) 382 | // * Use hset to set the fr (first receive) time 383 | // * Return the message and the counters 384 | // 385 | // Returns: 386 | // 387 | // {id, message, rc, fr} 388 | 389 | const script_receiveMessage = `local msg = redis.call("ZRANGEBYSCORE", KEYS[1], "-inf", KEYS[2], "LIMIT", "0", "1") 390 | if #msg == 0 then 391 | return {} 392 | end 393 | redis.call("ZADD", KEYS[1], KEYS[3], msg[1]) 394 | redis.call("HINCRBY", KEYS[1] .. ":Q", "totalrecv", 1) 395 | local mbody = redis.call("HGET", KEYS[1] .. ":Q", msg[1]) 396 | local rc = redis.call("HINCRBY", KEYS[1] .. ":Q", msg[1] .. ":rc", 1) 397 | local o = {msg[1], mbody, rc} 398 | if rc==1 then 399 | redis.call("HSET", KEYS[1] .. ":Q", msg[1] .. ":fr", KEYS[2]) 400 | table.insert(o, KEYS[2]) 401 | else 402 | local fr = redis.call("HGET", KEYS[1] .. ":Q", msg[1] .. ":fr") 403 | table.insert(o, fr) 404 | end 405 | return o`; 406 | 407 | // The changeMessageVisibility LUA Script 408 | // 409 | // Parameters: 410 | // 411 | // KEYS[1]: the zset key 412 | // KEYS[2]: the message id 413 | // 414 | // 415 | // * Find the message id 416 | // * Set the new timer 417 | // 418 | // Returns: 419 | // 420 | // 0 or 1 421 | 422 | const script_changeMessageVisibility = `local msg = redis.call("ZSCORE", KEYS[1], KEYS[2]) 423 | if not msg then 424 | return 0 425 | end 426 | redis.call("ZADD", KEYS[1], KEYS[3], KEYS[2]) 427 | return 1` 428 | 429 | this.redis.script("load", script_popMessage, (err, resp) => { 430 | if (err) { console.log(err); return; } 431 | this.popMessage_sha1 = resp; 432 | this.emit("scriptload:popMessage"); 433 | }); 434 | 435 | this.redis.script("load", script_receiveMessage, (err, resp) => { 436 | if (err) { console.log(err); return; } 437 | this.receiveMessage_sha1 = resp; 438 | this.emit("scriptload:receiveMessage"); 439 | }); 440 | 441 | this.redis.script("load", script_changeMessageVisibility, (err, resp) => { 442 | if (err) { console.log(err); return; } 443 | this.changeMessageVisibility_sha1 = resp; 444 | this.emit('scriptload:changeMessageVisibility'); 445 | }); 446 | } 447 | 448 | public listQueues = (cb) => { 449 | this.redis.smembers(`${this.redisns}QUEUES`, (err, resp) => { 450 | if (err) { this._handleError(cb, err); return; } 451 | cb(null, resp) 452 | }); 453 | } 454 | 455 | public popMessage = (options, cb) => { 456 | if (this._validate(options, ["qname"],cb) === false) 457 | return; 458 | 459 | this._getQueue(options.qname, false, (err, q) => { 460 | if (err) { this._handleError(cb, err); return; } 461 | // Make really sure that the LUA script is loaded 462 | if (this.popMessage_sha1) { 463 | this._popMessage(options, q, cb); 464 | return; 465 | } 466 | this.on("scriptload:popMessage", () => { 467 | this._popMessage(options, q, cb); 468 | }); 469 | }); 470 | } 471 | 472 | public receiveMessage = (options, cb) => { 473 | if (this._validate(options, ["qname"], cb) === false) 474 | return; 475 | 476 | this._getQueue(options.qname, false, (err, q) => { 477 | if (err) { this._handleError(cb, err); return; } 478 | // Now that we got the default queue settings 479 | options.vt = options.vt != null ? options.vt : q.vt; 480 | 481 | if (this._validate(options, ["vt"], cb) === false) 482 | return; 483 | 484 | // Make really sure that the LUA script is loaded 485 | if (this.receiveMessage_sha1) { 486 | this._receiveMessage(options, q, cb) 487 | return; 488 | } 489 | this.on("scriptload:receiveMessage", () => { 490 | this._receiveMessage(options, q, cb) 491 | }); 492 | }); 493 | } 494 | 495 | private _popMessage = (options, q, cb) => { 496 | this.redis.evalsha(this.popMessage_sha1, 2, `${this.redisns}${options.qname}`, q.ts, this._handleReceivedMessage(cb)); 497 | } 498 | 499 | private _receiveMessage = (options, q, cb) => { 500 | this.redis.evalsha(this.receiveMessage_sha1, 3, `${this.redisns}${options.qname}`, q.ts, q.ts + options.vt * 1000, this._handleReceivedMessage(cb)); 501 | } 502 | 503 | public sendMessage = (options, cb) => { 504 | if (this._validate(options, ["qname"],cb) === false) 505 | return 506 | 507 | this._getQueue(options.qname, true, (err, q) => { 508 | if (err) { this._handleError(cb, err); return; } 509 | // Now that we got the default queue settings 510 | options.delay = options.delay != null ? options.delay : q.delay; 511 | 512 | if (this._validate(options, ["delay"],cb) === false) 513 | return; 514 | 515 | // Check the message 516 | if (typeof options.message !== "string") { 517 | this._handleError(cb, "messageNotString"); 518 | return; 519 | } 520 | // Check the message size 521 | if (q.maxsize !== -1 && options.message.length > q.maxsize) { 522 | this._handleError(cb, "messageTooLong"); 523 | return; 524 | } 525 | // Ready to store the message 526 | const key = `${this.redisns}${options.qname}`; 527 | const mc = [ 528 | ["zadd", key, q.ts + options.delay * 1000, q.uid], 529 | ["hset", `${key}:Q`, q.uid, options.message], 530 | ["hincrby", `${key}:Q`, "totalsent", 1] 531 | ]; 532 | 533 | if (this.realtime) { 534 | mc.push(["zcard", key]); 535 | } 536 | this.redis.multi(mc).exec( (err, resp) => { 537 | if (err) { 538 | this._handleError(cb, err); 539 | return; 540 | } 541 | if (this.realtime) { 542 | this.redis.publish(`${this.redisns}rt:${options.qname}`, resp[3]); 543 | } 544 | cb(null, q.uid); 545 | }); 546 | }); 547 | } 548 | 549 | public setQueueAttributes = (options, cb) => { 550 | const props = ["vt", "maxsize", "delay"] 551 | let k = [] 552 | for (let item of props) { 553 | if (options[item] != null) { 554 | k.push(item); 555 | } 556 | } 557 | 558 | // Not a single key was supplied 559 | if (k.length === 0) { 560 | this._handleError(cb, "noAttributeSupplied"); 561 | return; 562 | } 563 | 564 | if (this._validate(options, ["qname"].concat(k), cb) === false) 565 | return 566 | 567 | const key = `${this.redisns}${options.qname}`; 568 | this._getQueue(options.qname, false, (err, q) => { 569 | if (err) { this._handleError(cb, err); return; } 570 | this.redis.time( (err, resp) => { 571 | if (err) { 572 | this._handleError(cb, err); 573 | return; 574 | } 575 | const mc = [ 576 | ["hset", `${this.redisns}${options.qname}:Q`, "modified", resp[0]] 577 | ]; 578 | for (let item of k) { 579 | mc.push(["hset", `${this.redisns}${options.qname}:Q`, item, options[item]]) 580 | }; 581 | this.redis.multi(mc).exec( (err) => { 582 | if (err) { 583 | this._handleError(cb, err); 584 | return; 585 | } 586 | this.getQueueAttributes(options, cb); 587 | }) 588 | }); 589 | }); 590 | } 591 | 592 | // Helpers 593 | private _formatZeroPad (num, count) { 594 | return ((Math.pow(10, count) + num) + "").substr(1); 595 | } 596 | 597 | private _handleError = (cb, err, data = {}) => { 598 | // try to create a error Object with humanized message 599 | let _err = null; 600 | if (_.isString(err)) { 601 | _err = new Error(); 602 | _err.name = err; 603 | 604 | // _err.message = this._ERRORS?[err]?(data) || "unkown"; 605 | let ref = null; 606 | _err.message = ((ref = this._ERRORS) != null ? typeof ref[err] === "function" ? ref[err](data) : void 0 : void 0) || "unkown"; 607 | } 608 | else { 609 | _err = err 610 | } 611 | cb(_err) 612 | } 613 | 614 | private _initErrors = () => { 615 | this._ERRORS = {}; 616 | for (let key in this.ERRORS) { 617 | this._ERRORS[key] = _.template(this.ERRORS[key]); 618 | } 619 | } 620 | 621 | private _makeid (len) { 622 | let text = ""; 623 | const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 624 | let i = 0; 625 | for (i = 0; i < len; i++) { 626 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 627 | } 628 | return text; 629 | } 630 | 631 | private _VALID = { 632 | qname: /^([a-zA-Z0-9_-]){1,160}$/, 633 | id: /^([a-zA-Z0-9:]){32}$/ 634 | } 635 | 636 | private _validate = (o, items, cb) => { 637 | for (let item of items) { 638 | switch (item) { 639 | case "qname": 640 | case "id": 641 | if (!o[item]) { 642 | this._handleError(cb, "missingParameter", {item:item}); 643 | return false; 644 | } 645 | o[item] = o[item].toString() 646 | if (!this._VALID[item].test(o[item])) { 647 | this._handleError(cb, "invalidFormat", {item:item}); 648 | return false; 649 | } 650 | break; 651 | case "vt": 652 | case "delay": 653 | o[item] = parseInt(o[item],10); 654 | if (_.isNaN(o[item]) || !_.isNumber(o[item]) || o[item] < 0 || o[item] > 9999999) { 655 | this._handleError(cb, "invalidValue", {item: item, min: 0, max: 9999999}); 656 | return false; 657 | } 658 | break; 659 | case "maxsize": 660 | o[item] = parseInt(o[item], 10) 661 | if (_.isNaN(o[item]) || !_.isNumber(o[item]) || o[item] < 1024 || o[item] > 65536) { 662 | // Allow unlimited messages 663 | if (o[item] !== -1) { 664 | this._handleError(cb, "invalidValue", {item: item, min: 1024, max: 65536}); 665 | return false; 666 | } 667 | } 668 | break; 669 | } 670 | }; 671 | return o; 672 | } 673 | 674 | private ERRORS = { 675 | "noAttributeSupplied": "No attribute was supplied", 676 | "missingParameter": "No <%= item %> supplied", 677 | "invalidFormat": "Invalid <%= item %> format", 678 | "invalidValue": "<%= item %> must be between <%= min %> and <%= max %>", 679 | "messageNotString": "Message must be a string", 680 | "messageTooLong": "Message too long", 681 | "queueNotFound": "Queue not found", 682 | "queueExists": "Queue exists" 683 | } 684 | } 685 | module.exports = RedisSMQ; -------------------------------------------------------------------------------- /coffeelint.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrow_spacing": { 3 | "level": "ignore" 4 | }, 5 | "braces_spacing": { 6 | "level": "ignore", 7 | "spaces": 0, 8 | "empty_object_spaces": 0 9 | }, 10 | "camel_case_classes": { 11 | "level": "error" 12 | }, 13 | "coffeescript_error": { 14 | "level": "error" 15 | }, 16 | "colon_assignment_spacing": { 17 | "level": "ignore", 18 | "spacing": { 19 | "left": 0, 20 | "right": 0 21 | } 22 | }, 23 | "cyclomatic_complexity": { 24 | "level": "ignore", 25 | "value": 10 26 | }, 27 | "duplicate_key": { 28 | "level": "error" 29 | }, 30 | "empty_constructor_needs_parens": { 31 | "level": "ignore" 32 | }, 33 | "ensure_comprehensions": { 34 | "level": "warn" 35 | }, 36 | "eol_last": { 37 | "level": "ignore" 38 | }, 39 | "indentation": { 40 | "value": 2, 41 | "level": "ignore" 42 | }, 43 | "line_endings": { 44 | "level": "ignore", 45 | "value": "unix" 46 | }, 47 | "max_line_length": { 48 | "value": 250, 49 | "level": "error", 50 | "limitComments": false 51 | }, 52 | "missing_fat_arrows": { 53 | "level": "ignore", 54 | "is_strict": false 55 | }, 56 | "newlines_after_classes": { 57 | "value": 3, 58 | "level": "ignore" 59 | }, 60 | "no_backticks": { 61 | "level": "error" 62 | }, 63 | "no_debugger": { 64 | "level": "warn", 65 | "console": false 66 | }, 67 | "no_empty_functions": { 68 | "level": "ignore" 69 | }, 70 | "no_empty_param_list": { 71 | "level": "ignore" 72 | }, 73 | "no_implicit_braces": { 74 | "level": "ignore", 75 | "strict": true 76 | }, 77 | "no_implicit_parens": { 78 | "level": "ignore", 79 | "strict": true 80 | }, 81 | "no_interpolation_in_single_quotes": { 82 | "level": "ignore" 83 | }, 84 | "no_nested_string_interpolation": { 85 | "level": "warn" 86 | }, 87 | "no_plusplus": { 88 | "level": "ignore" 89 | }, 90 | "no_private_function_fat_arrows": { 91 | "level": "warn" 92 | }, 93 | "no_stand_alone_at": { 94 | "level": "ignore" 95 | }, 96 | "no_tabs": { 97 | "level": "ignore" 98 | }, 99 | "no_this": { 100 | "level": "ignore" 101 | }, 102 | "no_throwing_strings": { 103 | "level": "error" 104 | }, 105 | "no_trailing_semicolons": { 106 | "level": "error" 107 | }, 108 | "no_trailing_whitespace": { 109 | "level": "error", 110 | "allowed_in_comments": false, 111 | "allowed_in_empty_lines": true 112 | }, 113 | "no_unnecessary_double_quotes": { 114 | "level": "ignore" 115 | }, 116 | "no_unnecessary_fat_arrows": { 117 | "level": "warn" 118 | }, 119 | "non_empty_constructor_needs_parens": { 120 | "level": "ignore" 121 | }, 122 | "prefer_english_operator": { 123 | "level": "ignore", 124 | "doubleNotLevel": "ignore" 125 | }, 126 | "space_operators": { 127 | "level": "ignore" 128 | }, 129 | "spacing_after_comma": { 130 | "level": "ignore" 131 | }, 132 | "transform_messes_up_line_numbers": { 133 | "level": "warn" 134 | } 135 | } -------------------------------------------------------------------------------- /examples/simple-queue.js: -------------------------------------------------------------------------------- 1 | const RedisSMQ = require("../index"); 2 | const rsmq = new RedisSMQ( {host: "127.0.0.1", port: 6379, ns: "rsmq"} ); 3 | 4 | /* 5 | ====================================== 6 | Make a simple queue and send/receive messages 7 | ====================================== 8 | */ 9 | 10 | function main() { 11 | const queuename = "testqueue"; 12 | 13 | // create a queue 14 | rsmq.createQueue({ qname: queuename }, (err) => { 15 | if (err) { 16 | // if the error is `queueExists` we can keep going as it tells us that the queue is already there 17 | if (err.name !== "queueExists") { 18 | console.error(err); 19 | return; 20 | } else { 21 | console.log("queue exists.. resuming.."); 22 | } 23 | } 24 | 25 | // start sending messages every 2 seconds 26 | sendMessageLoop(queuename); 27 | // start checking for messages every 500ms 28 | receiveMessageLoop(queuename); 29 | }); 30 | } 31 | main(); 32 | 33 | function sendMessageLoop(queuename) { 34 | // push a message every 2 seconds into the queue 35 | setInterval(() => { 36 | // send the messages with a random delay between 0-5 seconds 37 | rsmq.sendMessage({ qname: queuename, message: `Hello World at ${new Date().toISOString()}`, delay: Math.floor(Math.random() * 6) }, (err) => { 38 | if (err) { 39 | console.error(err); 40 | return; 41 | } 42 | 43 | console.log("pushed new message into queue.."); 44 | }); 45 | }, 2000); 46 | } 47 | 48 | function receiveMessageLoop(queuename) { 49 | // check for new messages every 2.5 seconds 50 | setInterval(() => { 51 | // alternative to receiveMessage would be popMessage => receives the next message from the queue and deletes it. 52 | rsmq.receiveMessage({ qname: queuename }, (err, resp) => { 53 | if (err) { 54 | console.error(err); 55 | return; 56 | } 57 | 58 | // checks if a message has been received 59 | if (resp.id) { 60 | console.log("received message:", resp.message); 61 | 62 | // we are done with working on our message, we can now safely delete it 63 | rsmq.deleteMessage({ qname: queuename, id: resp.id }, (err) => { 64 | if (err) { 65 | console.error(err); 66 | return; 67 | } 68 | 69 | console.log("deleted message with id", resp.id); 70 | }); 71 | } else { 72 | console.log("no available message in queue.."); 73 | } 74 | }); 75 | }, 2500); 76 | } -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for rsmq 0.8.3 2 | // Project: rsmq 3 | // Definitions by: erdii https://github.com/erdii 4 | 5 | /*~ This is the module template file for class modules. 6 | *~ You should rename it to index.d.ts and place it in a folder with the same name as the module. 7 | *~ For example, if you were writing a file for "super-greeter", this 8 | *~ file should be 'super-greeter/index.d.ts' 9 | */ 10 | 11 | /*~ Note that ES6 modules cannot directly export class objects. 12 | *~ This file should be imported using the CommonJS-style: 13 | *~ import x = require('someLibrary'); 14 | *~ 15 | *~ Refer to the documentation to understand common 16 | *~ workarounds for this limitation of ES6 modules. 17 | */ 18 | 19 | import { ClientOpts, RedisClient } from "redis"; 20 | 21 | /*~ This declaration specifies that the class constructor function 22 | *~ is the exported object from the file 23 | */ 24 | export = RedisSMQ; 25 | 26 | /*~ Write your module's methods and properties in this class */ 27 | declare class RedisSMQ { 28 | constructor(options: RedisSMQ.ConstructorOptions); 29 | quit(cb: RedisSMQ.Callback): void; 30 | createQueue(opts: RedisSMQ.CreateQueueOptions, cb: RedisSMQ.Callback<1>): void; 31 | createQueueAsync(opts: RedisSMQ.CreateQueueOptions): Promise<1>; 32 | listQueues(cb: RedisSMQ.Callback): void; 33 | listQueuesAsync(): Promise; 34 | deleteQueue(opts: RedisSMQ.DeleteQueueOptions, cb: RedisSMQ.Callback<1>): void; 35 | deleteQueueAsync(opts: RedisSMQ.DeleteQueueOptions): Promise<1>; 36 | getQueueAttributes(opts: RedisSMQ.GetQueueAttributesOptions, cb: RedisSMQ.Callback): void; 37 | getQueueAttributesAsync(opts: RedisSMQ.GetQueueAttributesOptions): Promise; 38 | setQueueAttributes(opts: RedisSMQ.SetQueueAttributesOptions, cb: RedisSMQ.Callback): void; 39 | setQueueAttributesAsync(opts: RedisSMQ.SetQueueAttributesOptions): Promise; 40 | sendMessage(opts: RedisSMQ.SendMessageOptions, cb: RedisSMQ.Callback): void; 41 | sendMessageAsync(opts: RedisSMQ.SendMessageOptions): Promise; 42 | receiveMessage(opts: RedisSMQ.ReceiveMessageOptions, cb: RedisSMQ.Callback): void; 43 | receiveMessageAsync(opts: RedisSMQ.ReceiveMessageOptions): Promise; 44 | popMessage(opts: RedisSMQ.PopMessageOptions, cb: RedisSMQ.Callback): void; 45 | popMessageAsync(opts: RedisSMQ.PopMessageOptions): Promise; 46 | deleteMessage(opts: RedisSMQ.DeleteMessageOptions, cb: RedisSMQ.Callback<0|1>): void; 47 | deleteMessageAsync(opts: RedisSMQ.DeleteMessageOptions): Promise<0|1>; 48 | changeMessageVisibility(opts: RedisSMQ.ChangeMessageVisibilityOptions, cb: RedisSMQ.Callback<0|1>): void; 49 | changeMessageVisibilityAsync(opts: RedisSMQ.ChangeMessageVisibilityOptions): Promise<0|1>; 50 | } 51 | 52 | declare namespace RedisSMQ { 53 | export type Callback = (err: any, response: T) => void; 54 | 55 | export interface ConstructorOptions { 56 | realtime?: boolean; 57 | host?: string; 58 | port?: number; 59 | ns?: string; 60 | options?: ClientOpts; 61 | client?: RedisClient; 62 | password?: string; 63 | } 64 | 65 | interface BaseOptions { 66 | /** 67 | * The Queue name. 68 | * Maximum 160 characters; alphanumeric characters, hyphens (-), and underscores (_) are allowed. 69 | * 70 | * @type {string} 71 | * @memberof BaseQueueOptions 72 | */ 73 | qname: string; 74 | } 75 | 76 | export interface CreateQueueOptions extends BaseOptions { 77 | /** 78 | * *(Default: 30)* 79 | * The length of time, in seconds, that a message received from a queue will 80 | * be invisible to other receiving components when they ask to receive messages. 81 | * Allowed values: 0-9999999 (around 115 days) 82 | * 83 | * @type {number} 84 | * @memberof CreateQueueOptions 85 | */ 86 | vt?: number; 87 | 88 | /** 89 | * *(Default: 0)* 90 | * The time in seconds that the delivery of all new messages in the queue will be delayed. 91 | * Allowed values: 0-9999999 (around 115 days) 92 | * 93 | * @type {number} 94 | * @memberof CreateQueueOptions 95 | */ 96 | delay?: number; 97 | 98 | /** 99 | * *(Default: 65536)* 100 | * The maximum message size in bytes. 101 | * Allowed values: 1024-65536 and -1 (for unlimited size) 102 | * 103 | * @type {number} 104 | * @memberof CreateQueueOptions 105 | */ 106 | maxsize?: number; 107 | } 108 | 109 | export interface DeleteQueueOptions extends BaseOptions {} 110 | 111 | export interface GetQueueAttributesOptions extends BaseOptions {} 112 | 113 | export interface SetQueueAttributesOptions extends BaseOptions { 114 | /** 115 | * The length of time, in seconds, 116 | * that a message received from a queue will be invisible to other receiving components 117 | * when they ask to receive messages. 118 | * Allowed values: 0-9999999 (around 115 days) 119 | * 120 | * @type {number} 121 | * @memberof SetQueueAttributesOptions 122 | */ 123 | vt?: number; 124 | 125 | /** 126 | * The time in seconds that the delivery of all new messages in the queue will be delayed. 127 | * Allowed values: 0-9999999 (around 115 days) 128 | * 129 | * @type {number} 130 | * @memberof SetQueueAttributesOptions 131 | */ 132 | delay?: number; 133 | 134 | /** 135 | * The maximum message size in bytes. 136 | * Allowed values: 1024-65536 and -1 (for unlimited size) 137 | * 138 | * @type {number} 139 | * @memberof SetQueueAttributesOptions 140 | */ 141 | maxsize?: number; 142 | } 143 | 144 | 145 | export interface SendMessageOptions extends BaseOptions { 146 | /** 147 | * Message for the queue 148 | * 149 | * @type {string} 150 | * @memberof SendMessageOptions 151 | */ 152 | message: string; 153 | 154 | /** 155 | * *(Default: queue settings)* 156 | * The time in seconds that the delivery of the message will be delayed. 157 | * Allowed values: 0-9999999 (around 115 days) 158 | * 159 | * @type {number} 160 | * @memberof SendMessageOptions 161 | */ 162 | delay?: number; 163 | } 164 | 165 | export interface ReceiveMessageOptions extends BaseOptions { 166 | /** 167 | * *(Default: queue settings)* 168 | * The length of time, in seconds, that the received message will be invisible to others. 169 | * Allowed values: 0-9999999 (around 115 days) 170 | * 171 | * @type {number} 172 | * @memberof ReceiveMessageOptions 173 | */ 174 | vt?: number; 175 | } 176 | 177 | export interface PopMessageOptions extends BaseOptions {} 178 | 179 | export interface DeleteMessageOptions extends BaseOptions { 180 | /** 181 | * message id to delete. 182 | * 183 | * @type {string} 184 | * @memberof DeleteMessageOptions 185 | */ 186 | id: string; 187 | } 188 | 189 | export interface ChangeMessageVisibilityOptions extends BaseOptions { 190 | /** 191 | * message id to modify. 192 | * 193 | * @type {string} 194 | * @memberof DeleteMessageOptions 195 | */ 196 | id: string; 197 | 198 | /** 199 | * The length of time, in seconds, that this message will not be visible. 200 | * Allowed values: 0-9999999 (around 115 days) 201 | * 202 | * @type {number} 203 | * @memberof ChangeMessageVisibilityOptions 204 | */ 205 | vt: number; 206 | } 207 | 208 | 209 | 210 | export interface QueueMessage { 211 | /** 212 | * The message's contents. 213 | * 214 | * @type {string} 215 | * @memberof QueueMessage 216 | */ 217 | message: string; 218 | 219 | /** 220 | * The internal message id. 221 | * 222 | * @type {string} 223 | * @memberof QueueMessage 224 | */ 225 | id: string; 226 | 227 | /** 228 | * Timestamp of when this message was sent / created. 229 | * 230 | * @type {number} 231 | * @memberof QueueMessage 232 | */ 233 | sent: number; 234 | 235 | /** 236 | * Timestamp of when this message was first received. 237 | * 238 | * @type {number} 239 | * @memberof QueueMessage 240 | */ 241 | fr: number; 242 | 243 | /** 244 | * Number of times this message was received. 245 | * 246 | * @type {number} 247 | * @memberof QueueMessage 248 | */ 249 | rc: number; 250 | } 251 | 252 | export interface QueueAttributes { 253 | /** 254 | * The visibility timeout for the queue in seconds 255 | * 256 | * @type {number} 257 | * @memberof QueueAttributes 258 | */ 259 | vt: number; 260 | 261 | /** 262 | * The delay for new messages in seconds 263 | * 264 | * @type {number} 265 | * @memberof QueueAttributes 266 | */ 267 | delay: number; 268 | 269 | /** 270 | * The maximum size of a message in bytes 271 | * 272 | * @type {number} 273 | * @memberof QueueAttributes 274 | */ 275 | maxsize: number; 276 | 277 | /** 278 | * Total number of messages received from the queue 279 | * 280 | * @type {number} 281 | * @memberof QueueAttributes 282 | */ 283 | totalrecv: number; 284 | 285 | /** 286 | * Total number of messages sent to the queue 287 | * 288 | * @type {number} 289 | * @memberof QueueAttributes 290 | */ 291 | totalsent: number; 292 | 293 | /** 294 | * Timestamp (epoch in seconds) when the queue was created 295 | * 296 | * @type {number} 297 | * @memberof QueueAttributes 298 | */ 299 | created: number; 300 | 301 | /** 302 | * Timestamp (epoch in seconds) when the queue was last modified with `setQueueAttributes` 303 | * 304 | * @type {number} 305 | * @memberof QueueAttributes 306 | */ 307 | modified: number; 308 | 309 | /** 310 | * Current number of messages in the queue 311 | * 312 | * @type {number} 313 | * @memberof QueueAttributes 314 | */ 315 | msgs: number; 316 | 317 | /** 318 | * Current number of hidden / not visible messages. 319 | * A message can be hidden while "in flight" due to a `vt` parameter or when sent with a `delay` 320 | * 321 | * @type {number} 322 | * @memberof QueueAttributes 323 | */ 324 | hiddenmsgs: number; 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const _ = require("lodash"); 4 | const RedisInst = require("redis"); 5 | const EventEmitter = require("events").EventEmitter; 6 | class RedisSMQ extends EventEmitter { 7 | constructor(options = {}) { 8 | super(options); 9 | this.asyncify = (methodKey) => { 10 | const asyncMethodKey = methodKey + "Async"; 11 | this[asyncMethodKey] = (...args) => { 12 | return new Promise((resolve, reject) => { 13 | this[methodKey](...args, (err, result) => { 14 | if (err) { 15 | reject(err); 16 | return; 17 | } 18 | resolve(result); 19 | }); 20 | }); 21 | }; 22 | }; 23 | this.quit = (cb) => { 24 | if (cb === undefined) { 25 | cb = () => { }; 26 | } 27 | this.redis.quit(cb); 28 | }; 29 | this._getQueue = (qname, uid, cb) => { 30 | const mc = [ 31 | ["hmget", `${this.redisns}${qname}:Q`, "vt", "delay", "maxsize"], 32 | ["time"] 33 | ]; 34 | this.redis.multi(mc).exec((err, resp) => { 35 | if (err) { 36 | this._handleError(cb, err); 37 | return; 38 | } 39 | if (resp[0][0] === null || resp[0][1] === null || resp[0][2] === null) { 40 | this._handleError(cb, "queueNotFound"); 41 | return; 42 | } 43 | const ms = this._formatZeroPad(Number(resp[1][1]), 6); 44 | const ts = Number(resp[1][0] + ms.toString(10).slice(0, 3)); 45 | const q = { 46 | vt: parseInt(resp[0][0], 10), 47 | delay: parseInt(resp[0][1], 10), 48 | maxsize: parseInt(resp[0][2], 10), 49 | ts: ts 50 | }; 51 | if (uid) { 52 | uid = this._makeid(22); 53 | q.uid = Number(resp[1][0] + ms).toString(36) + uid; 54 | } 55 | cb(null, q); 56 | }); 57 | }; 58 | this.changeMessageVisibility = (options, cb) => { 59 | if (this._validate(options, ["qname", "id", "vt"], cb) === false) 60 | return; 61 | this._getQueue(options.qname, false, (err, q) => { 62 | if (err) { 63 | this._handleError(cb, err); 64 | return; 65 | } 66 | if (this.changeMessageVisibility_sha1) { 67 | this._changeMessageVisibility(options, q, cb); 68 | return; 69 | } 70 | this.on("scriptload:changeMessageVisibility", () => { 71 | this._changeMessageVisibility(options, q, cb); 72 | }); 73 | }); 74 | }; 75 | this._changeMessageVisibility = (options, q, cb) => { 76 | this.redis.evalsha(this.changeMessageVisibility_sha1, 3, `${this.redisns}${options.qname}`, options.id, q.ts + options.vt * 1000, (err, resp) => { 77 | if (err) { 78 | this._handleError(cb, err); 79 | return; 80 | } 81 | cb(null, resp); 82 | }); 83 | }; 84 | this.createQueue = (options, cb) => { 85 | const key = `${this.redisns}${options.qname}:Q`; 86 | options.vt = options.vt != null ? options.vt : 30; 87 | options.delay = options.delay != null ? options.delay : 0; 88 | options.maxsize = options.maxsize != null ? options.maxsize : 65536; 89 | if (this._validate(options, ["qname", "vt", "delay", "maxsize"], cb) === false) 90 | return; 91 | this.redis.time((err, resp) => { 92 | if (err) { 93 | this._handleError(cb, err); 94 | return; 95 | } 96 | const mc = [ 97 | ["hsetnx", key, "vt", options.vt], 98 | ["hsetnx", key, "delay", options.delay], 99 | ["hsetnx", key, "maxsize", options.maxsize], 100 | ["hsetnx", key, "created", resp[0]], 101 | ["hsetnx", key, "modified", resp[0]], 102 | ]; 103 | this.redis.multi(mc).exec((err, resp) => { 104 | if (err) { 105 | this._handleError(cb, err); 106 | return; 107 | } 108 | if (resp[0] === 0) { 109 | this._handleError(cb, "queueExists"); 110 | return; 111 | } 112 | this.redis.sadd(`${this.redisns}QUEUES`, options.qname, (err, resp) => { 113 | if (err) { 114 | this._handleError(cb, err); 115 | return; 116 | } 117 | cb(null, 1); 118 | }); 119 | }); 120 | }); 121 | }; 122 | this.deleteMessage = (options, cb) => { 123 | if (this._validate(options, ["qname", "id"], cb) === false) 124 | return; 125 | const key = `${this.redisns}${options.qname}`; 126 | const mc = [ 127 | ["zrem", key, options.id], 128 | ["hdel", `${key}:Q`, `${options.id}`, `${options.id}:rc`, `${options.id}:fr`] 129 | ]; 130 | this.redis.multi(mc).exec((err, resp) => { 131 | if (err) { 132 | this._handleError(cb, err); 133 | return; 134 | } 135 | if (resp[0] === 1 && resp[1] > 0) { 136 | cb(null, 1); 137 | } 138 | else { 139 | cb(null, 0); 140 | } 141 | }); 142 | }; 143 | this.deleteQueue = (options, cb) => { 144 | if (this._validate(options, ["qname"], cb) === false) 145 | return; 146 | const key = `${this.redisns}${options.qname}`; 147 | const mc = [ 148 | ["del", `${key}:Q`, key], 149 | ["srem", `${this.redisns}QUEUES`, options.qname] 150 | ]; 151 | this.redis.multi(mc).exec((err, resp) => { 152 | if (err) { 153 | this._handleError(cb, err); 154 | return; 155 | } 156 | if (resp[0] === 0) { 157 | this._handleError(cb, "queueNotFound"); 158 | return; 159 | } 160 | cb(null, 1); 161 | }); 162 | }; 163 | this.getQueueAttributes = (options, cb) => { 164 | if (this._validate(options, ["qname"], cb) === false) 165 | return; 166 | const key = `${this.redisns}${options.qname}`; 167 | this.redis.time((err, resp) => { 168 | if (err) { 169 | this._handleError(cb, err); 170 | return; 171 | } 172 | const mc = [ 173 | ["hmget", `${key}:Q`, "vt", "delay", "maxsize", "totalrecv", "totalsent", "created", "modified"], 174 | ["zcard", key], 175 | ["zcount", key, resp[0] + "000", "+inf"] 176 | ]; 177 | this.redis.multi(mc).exec((err, resp) => { 178 | if (err) { 179 | this._handleError(cb, err); 180 | return; 181 | } 182 | if (resp[0][0] === null) { 183 | this._handleError(cb, "queueNotFound"); 184 | return; 185 | } 186 | const o = { 187 | vt: parseInt(resp[0][0], 10), 188 | delay: parseInt(resp[0][1], 10), 189 | maxsize: parseInt(resp[0][2], 10), 190 | totalrecv: parseInt(resp[0][3], 10) || 0, 191 | totalsent: parseInt(resp[0][4], 10) || 0, 192 | created: parseInt(resp[0][5], 10), 193 | modified: parseInt(resp[0][6], 10), 194 | msgs: resp[1], 195 | hiddenmsgs: resp[2] 196 | }; 197 | cb(null, o); 198 | }); 199 | }); 200 | }; 201 | this._handleReceivedMessage = (cb) => { 202 | return (err, resp) => { 203 | if (err) { 204 | this._handleError(cb, err); 205 | return; 206 | } 207 | if (!resp.length) { 208 | cb(null, {}); 209 | return; 210 | } 211 | const o = { 212 | id: resp[0], 213 | message: resp[1], 214 | rc: resp[2], 215 | fr: Number(resp[3]), 216 | sent: Number(parseInt(resp[0].slice(0, 10), 36) / 1000) 217 | }; 218 | cb(null, o); 219 | }; 220 | }; 221 | this.initScript = () => { 222 | const script_popMessage = `local msg = redis.call("ZRANGEBYSCORE", KEYS[1], "-inf", KEYS[2], "LIMIT", "0", "1") 223 | if #msg == 0 then 224 | return {} 225 | end 226 | redis.call("HINCRBY", KEYS[1] .. ":Q", "totalrecv", 1) 227 | local mbody = redis.call("HGET", KEYS[1] .. ":Q", msg[1]) 228 | local rc = redis.call("HINCRBY", KEYS[1] .. ":Q", msg[1] .. ":rc", 1) 229 | local o = {msg[1], mbody, rc} 230 | if rc==1 then 231 | table.insert(o, KEYS[2]) 232 | else 233 | local fr = redis.call("HGET", KEYS[1] .. ":Q", msg[1] .. ":fr") 234 | table.insert(o, fr) 235 | end 236 | redis.call("ZREM", KEYS[1], msg[1]) 237 | redis.call("HDEL", KEYS[1] .. ":Q", msg[1], msg[1] .. ":rc", msg[1] .. ":fr") 238 | return o`; 239 | const script_receiveMessage = `local msg = redis.call("ZRANGEBYSCORE", KEYS[1], "-inf", KEYS[2], "LIMIT", "0", "1") 240 | if #msg == 0 then 241 | return {} 242 | end 243 | redis.call("ZADD", KEYS[1], KEYS[3], msg[1]) 244 | redis.call("HINCRBY", KEYS[1] .. ":Q", "totalrecv", 1) 245 | local mbody = redis.call("HGET", KEYS[1] .. ":Q", msg[1]) 246 | local rc = redis.call("HINCRBY", KEYS[1] .. ":Q", msg[1] .. ":rc", 1) 247 | local o = {msg[1], mbody, rc} 248 | if rc==1 then 249 | redis.call("HSET", KEYS[1] .. ":Q", msg[1] .. ":fr", KEYS[2]) 250 | table.insert(o, KEYS[2]) 251 | else 252 | local fr = redis.call("HGET", KEYS[1] .. ":Q", msg[1] .. ":fr") 253 | table.insert(o, fr) 254 | end 255 | return o`; 256 | const script_changeMessageVisibility = `local msg = redis.call("ZSCORE", KEYS[1], KEYS[2]) 257 | if not msg then 258 | return 0 259 | end 260 | redis.call("ZADD", KEYS[1], KEYS[3], KEYS[2]) 261 | return 1`; 262 | this.redis.script("load", script_popMessage, (err, resp) => { 263 | if (err) { 264 | console.log(err); 265 | return; 266 | } 267 | this.popMessage_sha1 = resp; 268 | this.emit("scriptload:popMessage"); 269 | }); 270 | this.redis.script("load", script_receiveMessage, (err, resp) => { 271 | if (err) { 272 | console.log(err); 273 | return; 274 | } 275 | this.receiveMessage_sha1 = resp; 276 | this.emit("scriptload:receiveMessage"); 277 | }); 278 | this.redis.script("load", script_changeMessageVisibility, (err, resp) => { 279 | if (err) { 280 | console.log(err); 281 | return; 282 | } 283 | this.changeMessageVisibility_sha1 = resp; 284 | this.emit('scriptload:changeMessageVisibility'); 285 | }); 286 | }; 287 | this.listQueues = (cb) => { 288 | this.redis.smembers(`${this.redisns}QUEUES`, (err, resp) => { 289 | if (err) { 290 | this._handleError(cb, err); 291 | return; 292 | } 293 | cb(null, resp); 294 | }); 295 | }; 296 | this.popMessage = (options, cb) => { 297 | if (this._validate(options, ["qname"], cb) === false) 298 | return; 299 | this._getQueue(options.qname, false, (err, q) => { 300 | if (err) { 301 | this._handleError(cb, err); 302 | return; 303 | } 304 | if (this.popMessage_sha1) { 305 | this._popMessage(options, q, cb); 306 | return; 307 | } 308 | this.on("scriptload:popMessage", () => { 309 | this._popMessage(options, q, cb); 310 | }); 311 | }); 312 | }; 313 | this.receiveMessage = (options, cb) => { 314 | if (this._validate(options, ["qname"], cb) === false) 315 | return; 316 | this._getQueue(options.qname, false, (err, q) => { 317 | if (err) { 318 | this._handleError(cb, err); 319 | return; 320 | } 321 | options.vt = options.vt != null ? options.vt : q.vt; 322 | if (this._validate(options, ["vt"], cb) === false) 323 | return; 324 | if (this.receiveMessage_sha1) { 325 | this._receiveMessage(options, q, cb); 326 | return; 327 | } 328 | this.on("scriptload:receiveMessage", () => { 329 | this._receiveMessage(options, q, cb); 330 | }); 331 | }); 332 | }; 333 | this._popMessage = (options, q, cb) => { 334 | this.redis.evalsha(this.popMessage_sha1, 2, `${this.redisns}${options.qname}`, q.ts, this._handleReceivedMessage(cb)); 335 | }; 336 | this._receiveMessage = (options, q, cb) => { 337 | this.redis.evalsha(this.receiveMessage_sha1, 3, `${this.redisns}${options.qname}`, q.ts, q.ts + options.vt * 1000, this._handleReceivedMessage(cb)); 338 | }; 339 | this.sendMessage = (options, cb) => { 340 | if (this._validate(options, ["qname"], cb) === false) 341 | return; 342 | this._getQueue(options.qname, true, (err, q) => { 343 | if (err) { 344 | this._handleError(cb, err); 345 | return; 346 | } 347 | options.delay = options.delay != null ? options.delay : q.delay; 348 | if (this._validate(options, ["delay"], cb) === false) 349 | return; 350 | if (typeof options.message !== "string") { 351 | this._handleError(cb, "messageNotString"); 352 | return; 353 | } 354 | if (q.maxsize !== -1 && options.message.length > q.maxsize) { 355 | this._handleError(cb, "messageTooLong"); 356 | return; 357 | } 358 | const key = `${this.redisns}${options.qname}`; 359 | const mc = [ 360 | ["zadd", key, q.ts + options.delay * 1000, q.uid], 361 | ["hset", `${key}:Q`, q.uid, options.message], 362 | ["hincrby", `${key}:Q`, "totalsent", 1] 363 | ]; 364 | if (this.realtime) { 365 | mc.push(["zcard", key]); 366 | } 367 | this.redis.multi(mc).exec((err, resp) => { 368 | if (err) { 369 | this._handleError(cb, err); 370 | return; 371 | } 372 | if (this.realtime) { 373 | this.redis.publish(`${this.redisns}rt:${options.qname}`, resp[3]); 374 | } 375 | cb(null, q.uid); 376 | }); 377 | }); 378 | }; 379 | this.setQueueAttributes = (options, cb) => { 380 | const props = ["vt", "maxsize", "delay"]; 381 | let k = []; 382 | for (let item of props) { 383 | if (options[item] != null) { 384 | k.push(item); 385 | } 386 | } 387 | if (k.length === 0) { 388 | this._handleError(cb, "noAttributeSupplied"); 389 | return; 390 | } 391 | if (this._validate(options, ["qname"].concat(k), cb) === false) 392 | return; 393 | const key = `${this.redisns}${options.qname}`; 394 | this._getQueue(options.qname, false, (err, q) => { 395 | if (err) { 396 | this._handleError(cb, err); 397 | return; 398 | } 399 | this.redis.time((err, resp) => { 400 | if (err) { 401 | this._handleError(cb, err); 402 | return; 403 | } 404 | const mc = [ 405 | ["hset", `${this.redisns}${options.qname}:Q`, "modified", resp[0]] 406 | ]; 407 | for (let item of k) { 408 | mc.push(["hset", `${this.redisns}${options.qname}:Q`, item, options[item]]); 409 | } 410 | ; 411 | this.redis.multi(mc).exec((err) => { 412 | if (err) { 413 | this._handleError(cb, err); 414 | return; 415 | } 416 | this.getQueueAttributes(options, cb); 417 | }); 418 | }); 419 | }); 420 | }; 421 | this._handleError = (cb, err, data = {}) => { 422 | let _err = null; 423 | if (_.isString(err)) { 424 | _err = new Error(); 425 | _err.name = err; 426 | let ref = null; 427 | _err.message = ((ref = this._ERRORS) != null ? typeof ref[err] === "function" ? ref[err](data) : void 0 : void 0) || "unkown"; 428 | } 429 | else { 430 | _err = err; 431 | } 432 | cb(_err); 433 | }; 434 | this._initErrors = () => { 435 | this._ERRORS = {}; 436 | for (let key in this.ERRORS) { 437 | this._ERRORS[key] = _.template(this.ERRORS[key]); 438 | } 439 | }; 440 | this._VALID = { 441 | qname: /^([a-zA-Z0-9_-]){1,160}$/, 442 | id: /^([a-zA-Z0-9:]){32}$/ 443 | }; 444 | this._validate = (o, items, cb) => { 445 | for (let item of items) { 446 | switch (item) { 447 | case "qname": 448 | case "id": 449 | if (!o[item]) { 450 | this._handleError(cb, "missingParameter", { item: item }); 451 | return false; 452 | } 453 | o[item] = o[item].toString(); 454 | if (!this._VALID[item].test(o[item])) { 455 | this._handleError(cb, "invalidFormat", { item: item }); 456 | return false; 457 | } 458 | break; 459 | case "vt": 460 | case "delay": 461 | o[item] = parseInt(o[item], 10); 462 | if (_.isNaN(o[item]) || !_.isNumber(o[item]) || o[item] < 0 || o[item] > 9999999) { 463 | this._handleError(cb, "invalidValue", { item: item, min: 0, max: 9999999 }); 464 | return false; 465 | } 466 | break; 467 | case "maxsize": 468 | o[item] = parseInt(o[item], 10); 469 | if (_.isNaN(o[item]) || !_.isNumber(o[item]) || o[item] < 1024 || o[item] > 65536) { 470 | if (o[item] !== -1) { 471 | this._handleError(cb, "invalidValue", { item: item, min: 1024, max: 65536 }); 472 | return false; 473 | } 474 | } 475 | break; 476 | } 477 | } 478 | ; 479 | return o; 480 | }; 481 | this.ERRORS = { 482 | "noAttributeSupplied": "No attribute was supplied", 483 | "missingParameter": "No <%= item %> supplied", 484 | "invalidFormat": "Invalid <%= item %> format", 485 | "invalidValue": "<%= item %> must be between <%= min %> and <%= max %>", 486 | "messageNotString": "Message must be a string", 487 | "messageTooLong": "Message too long", 488 | "queueNotFound": "Queue not found", 489 | "queueExists": "Queue exists" 490 | }; 491 | if (Promise) { 492 | _.forEach([ 493 | "changeMessageVisibility", 494 | "createQueue", 495 | "deleteMessage", 496 | "deleteQueue", 497 | "getQueueAttributes", 498 | "listQueues", 499 | "popMessage", 500 | "receiveMessage", 501 | "sendMessage", 502 | "setQueueAttributes", 503 | "quit" 504 | ], this.asyncify); 505 | } 506 | const opts = _.extend({ 507 | host: "127.0.0.1", 508 | port: 6379, 509 | options: { 510 | password: options.password || null 511 | }, 512 | client: null, 513 | ns: "rsmq", 514 | realtime: false 515 | }, options); 516 | opts.options.host = opts.host; 517 | opts.options.port = opts.port; 518 | this.realtime = opts.realtime; 519 | this.redisns = opts.ns + ":"; 520 | if (opts.client && options.client.constructor.name === "RedisClient") { 521 | this.redis = opts.client; 522 | } 523 | else { 524 | this.redis = RedisInst.createClient(opts); 525 | } 526 | this.connected = this.redis.connected || false; 527 | if (this.connected) { 528 | this.emit("connect"); 529 | this.initScript(); 530 | } 531 | this.redis.on("connect", () => { 532 | this.connected = true; 533 | this.emit("connect"); 534 | this.initScript(); 535 | }); 536 | this.redis.on("error", (err) => { 537 | if (err.message.indexOf("ECONNREFUSED")) { 538 | this.connected = false; 539 | this.emit("disconnect"); 540 | } 541 | else { 542 | console.error("Redis ERROR", err); 543 | this.emit("error"); 544 | } 545 | }); 546 | this._initErrors(); 547 | } 548 | _formatZeroPad(num, count) { 549 | return ((Math.pow(10, count) + num) + "").substr(1); 550 | } 551 | _makeid(len) { 552 | let text = ""; 553 | const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 554 | let i = 0; 555 | for (i = 0; i < len; i++) { 556 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 557 | } 558 | return text; 559 | } 560 | } 561 | module.exports = RedisSMQ; 562 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rsmq", 3 | "version": "0.12.4", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "15.12.2", 9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz", 10 | "integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww==", 11 | "dev": true 12 | }, 13 | "@types/redis": { 14 | "version": "2.8.29", 15 | "resolved": "https://registry.npmjs.org/@types/redis/-/redis-2.8.29.tgz", 16 | "integrity": "sha512-/pjQ9lwnL/t1bEfRHQFEJB3kHCR65tpB19NEWmbqcgGgqrfeGo/9b4tUtHbClxQoy3+g6Esx2QRtV7fk7kBPYg==", 17 | "dev": true, 18 | "requires": { 19 | "@types/node": "*" 20 | } 21 | }, 22 | "ansi-colors": { 23 | "version": "3.2.3", 24 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", 25 | "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", 26 | "dev": true 27 | }, 28 | "ansi-regex": { 29 | "version": "3.0.0", 30 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 31 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", 32 | "dev": true 33 | }, 34 | "ansi-styles": { 35 | "version": "3.2.1", 36 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 37 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 38 | "dev": true, 39 | "requires": { 40 | "color-convert": "^1.9.0" 41 | } 42 | }, 43 | "anymatch": { 44 | "version": "3.1.2", 45 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", 46 | "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", 47 | "dev": true, 48 | "requires": { 49 | "normalize-path": "^3.0.0", 50 | "picomatch": "^2.0.4" 51 | } 52 | }, 53 | "argparse": { 54 | "version": "1.0.10", 55 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 56 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 57 | "dev": true, 58 | "requires": { 59 | "sprintf-js": "~1.0.2" 60 | } 61 | }, 62 | "async": { 63 | "version": "3.2.0", 64 | "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", 65 | "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", 66 | "dev": true 67 | }, 68 | "balanced-match": { 69 | "version": "1.0.2", 70 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 71 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 72 | "dev": true 73 | }, 74 | "big.js": { 75 | "version": "5.2.2", 76 | "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", 77 | "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", 78 | "dev": true 79 | }, 80 | "binary-extensions": { 81 | "version": "2.2.0", 82 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 83 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 84 | "dev": true 85 | }, 86 | "brace-expansion": { 87 | "version": "1.1.11", 88 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 89 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 90 | "dev": true, 91 | "requires": { 92 | "balanced-match": "^1.0.0", 93 | "concat-map": "0.0.1" 94 | } 95 | }, 96 | "braces": { 97 | "version": "3.0.2", 98 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 99 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 100 | "dev": true, 101 | "requires": { 102 | "fill-range": "^7.0.1" 103 | } 104 | }, 105 | "browser-stdout": { 106 | "version": "1.3.1", 107 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 108 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 109 | "dev": true 110 | }, 111 | "call-bind": { 112 | "version": "1.0.2", 113 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 114 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 115 | "dev": true, 116 | "requires": { 117 | "function-bind": "^1.1.1", 118 | "get-intrinsic": "^1.0.2" 119 | } 120 | }, 121 | "camelcase": { 122 | "version": "5.3.1", 123 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", 124 | "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", 125 | "dev": true 126 | }, 127 | "chalk": { 128 | "version": "2.4.2", 129 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 130 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 131 | "dev": true, 132 | "requires": { 133 | "ansi-styles": "^3.2.1", 134 | "escape-string-regexp": "^1.0.5", 135 | "supports-color": "^5.3.0" 136 | }, 137 | "dependencies": { 138 | "supports-color": { 139 | "version": "5.5.0", 140 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 141 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 142 | "dev": true, 143 | "requires": { 144 | "has-flag": "^3.0.0" 145 | } 146 | } 147 | } 148 | }, 149 | "chokidar": { 150 | "version": "3.3.0", 151 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", 152 | "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", 153 | "dev": true, 154 | "requires": { 155 | "anymatch": "~3.1.1", 156 | "braces": "~3.0.2", 157 | "fsevents": "~2.1.1", 158 | "glob-parent": "~5.1.0", 159 | "is-binary-path": "~2.1.0", 160 | "is-glob": "~4.0.1", 161 | "normalize-path": "~3.0.0", 162 | "readdirp": "~3.2.0" 163 | } 164 | }, 165 | "cliui": { 166 | "version": "5.0.0", 167 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", 168 | "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", 169 | "dev": true, 170 | "requires": { 171 | "string-width": "^3.1.0", 172 | "strip-ansi": "^5.2.0", 173 | "wrap-ansi": "^5.1.0" 174 | }, 175 | "dependencies": { 176 | "ansi-regex": { 177 | "version": "4.1.0", 178 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 179 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", 180 | "dev": true 181 | }, 182 | "string-width": { 183 | "version": "3.1.0", 184 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 185 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 186 | "dev": true, 187 | "requires": { 188 | "emoji-regex": "^7.0.1", 189 | "is-fullwidth-code-point": "^2.0.0", 190 | "strip-ansi": "^5.1.0" 191 | } 192 | }, 193 | "strip-ansi": { 194 | "version": "5.2.0", 195 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 196 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 197 | "dev": true, 198 | "requires": { 199 | "ansi-regex": "^4.1.0" 200 | } 201 | } 202 | } 203 | }, 204 | "coffeescript": { 205 | "version": "2.5.1", 206 | "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.5.1.tgz", 207 | "integrity": "sha512-J2jRPX0eeFh5VKyVnoLrfVFgLZtnnmp96WQSLAS8OrLm2wtQLcnikYKe1gViJKDH7vucjuhHvBKKBP3rKcD1tQ==", 208 | "dev": true 209 | }, 210 | "color-convert": { 211 | "version": "1.9.3", 212 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 213 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 214 | "dev": true, 215 | "requires": { 216 | "color-name": "1.1.3" 217 | } 218 | }, 219 | "color-name": { 220 | "version": "1.1.3", 221 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 222 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 223 | "dev": true 224 | }, 225 | "concat-map": { 226 | "version": "0.0.1", 227 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 228 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 229 | "dev": true 230 | }, 231 | "core-util-is": { 232 | "version": "1.0.2", 233 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 234 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", 235 | "dev": true 236 | }, 237 | "debug": { 238 | "version": "3.2.6", 239 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", 240 | "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", 241 | "dev": true, 242 | "requires": { 243 | "ms": "^2.1.1" 244 | } 245 | }, 246 | "decamelize": { 247 | "version": "1.2.0", 248 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 249 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", 250 | "dev": true 251 | }, 252 | "define-properties": { 253 | "version": "1.1.3", 254 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", 255 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", 256 | "dev": true, 257 | "requires": { 258 | "object-keys": "^1.0.12" 259 | } 260 | }, 261 | "denque": { 262 | "version": "1.5.0", 263 | "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", 264 | "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==" 265 | }, 266 | "diff": { 267 | "version": "3.5.0", 268 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 269 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 270 | "dev": true 271 | }, 272 | "emoji-regex": { 273 | "version": "7.0.3", 274 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", 275 | "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", 276 | "dev": true 277 | }, 278 | "emojis-list": { 279 | "version": "3.0.0", 280 | "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", 281 | "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", 282 | "dev": true 283 | }, 284 | "enhanced-resolve": { 285 | "version": "4.5.0", 286 | "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", 287 | "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", 288 | "dev": true, 289 | "requires": { 290 | "graceful-fs": "^4.1.2", 291 | "memory-fs": "^0.5.0", 292 | "tapable": "^1.0.0" 293 | } 294 | }, 295 | "errno": { 296 | "version": "0.1.8", 297 | "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", 298 | "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", 299 | "dev": true, 300 | "requires": { 301 | "prr": "~1.0.1" 302 | } 303 | }, 304 | "es-abstract": { 305 | "version": "1.18.3", 306 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", 307 | "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==", 308 | "dev": true, 309 | "requires": { 310 | "call-bind": "^1.0.2", 311 | "es-to-primitive": "^1.2.1", 312 | "function-bind": "^1.1.1", 313 | "get-intrinsic": "^1.1.1", 314 | "has": "^1.0.3", 315 | "has-symbols": "^1.0.2", 316 | "is-callable": "^1.2.3", 317 | "is-negative-zero": "^2.0.1", 318 | "is-regex": "^1.1.3", 319 | "is-string": "^1.0.6", 320 | "object-inspect": "^1.10.3", 321 | "object-keys": "^1.1.1", 322 | "object.assign": "^4.1.2", 323 | "string.prototype.trimend": "^1.0.4", 324 | "string.prototype.trimstart": "^1.0.4", 325 | "unbox-primitive": "^1.0.1" 326 | }, 327 | "dependencies": { 328 | "object.assign": { 329 | "version": "4.1.2", 330 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", 331 | "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", 332 | "dev": true, 333 | "requires": { 334 | "call-bind": "^1.0.0", 335 | "define-properties": "^1.1.3", 336 | "has-symbols": "^1.0.1", 337 | "object-keys": "^1.1.1" 338 | } 339 | } 340 | } 341 | }, 342 | "es-to-primitive": { 343 | "version": "1.2.1", 344 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", 345 | "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", 346 | "dev": true, 347 | "requires": { 348 | "is-callable": "^1.1.4", 349 | "is-date-object": "^1.0.1", 350 | "is-symbol": "^1.0.2" 351 | } 352 | }, 353 | "escape-string-regexp": { 354 | "version": "1.0.5", 355 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 356 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 357 | "dev": true 358 | }, 359 | "esprima": { 360 | "version": "4.0.1", 361 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 362 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 363 | "dev": true 364 | }, 365 | "fill-range": { 366 | "version": "7.0.1", 367 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 368 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 369 | "dev": true, 370 | "requires": { 371 | "to-regex-range": "^5.0.1" 372 | } 373 | }, 374 | "find-up": { 375 | "version": "3.0.0", 376 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", 377 | "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", 378 | "dev": true, 379 | "requires": { 380 | "locate-path": "^3.0.0" 381 | } 382 | }, 383 | "flat": { 384 | "version": "4.1.1", 385 | "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", 386 | "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", 387 | "dev": true, 388 | "requires": { 389 | "is-buffer": "~2.0.3" 390 | } 391 | }, 392 | "fs.realpath": { 393 | "version": "1.0.0", 394 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 395 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 396 | "dev": true 397 | }, 398 | "fsevents": { 399 | "version": "2.1.3", 400 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", 401 | "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", 402 | "dev": true, 403 | "optional": true 404 | }, 405 | "function-bind": { 406 | "version": "1.1.1", 407 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 408 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 409 | "dev": true 410 | }, 411 | "get-caller-file": { 412 | "version": "2.0.5", 413 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 414 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 415 | "dev": true 416 | }, 417 | "get-intrinsic": { 418 | "version": "1.1.1", 419 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", 420 | "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", 421 | "dev": true, 422 | "requires": { 423 | "function-bind": "^1.1.1", 424 | "has": "^1.0.3", 425 | "has-symbols": "^1.0.1" 426 | } 427 | }, 428 | "glob": { 429 | "version": "7.1.3", 430 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", 431 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", 432 | "dev": true, 433 | "requires": { 434 | "fs.realpath": "^1.0.0", 435 | "inflight": "^1.0.4", 436 | "inherits": "2", 437 | "minimatch": "^3.0.4", 438 | "once": "^1.3.0", 439 | "path-is-absolute": "^1.0.0" 440 | } 441 | }, 442 | "glob-parent": { 443 | "version": "5.1.2", 444 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 445 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 446 | "dev": true, 447 | "requires": { 448 | "is-glob": "^4.0.1" 449 | } 450 | }, 451 | "graceful-fs": { 452 | "version": "4.2.6", 453 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", 454 | "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", 455 | "dev": true 456 | }, 457 | "growl": { 458 | "version": "1.10.5", 459 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 460 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", 461 | "dev": true 462 | }, 463 | "has": { 464 | "version": "1.0.3", 465 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 466 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 467 | "dev": true, 468 | "requires": { 469 | "function-bind": "^1.1.1" 470 | } 471 | }, 472 | "has-bigints": { 473 | "version": "1.0.1", 474 | "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", 475 | "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", 476 | "dev": true 477 | }, 478 | "has-flag": { 479 | "version": "3.0.0", 480 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 481 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 482 | "dev": true 483 | }, 484 | "has-symbols": { 485 | "version": "1.0.2", 486 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", 487 | "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", 488 | "dev": true 489 | }, 490 | "he": { 491 | "version": "1.2.0", 492 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 493 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 494 | "dev": true 495 | }, 496 | "inflight": { 497 | "version": "1.0.6", 498 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 499 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 500 | "dev": true, 501 | "requires": { 502 | "once": "^1.3.0", 503 | "wrappy": "1" 504 | } 505 | }, 506 | "inherits": { 507 | "version": "2.0.4", 508 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 509 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 510 | "dev": true 511 | }, 512 | "is-bigint": { 513 | "version": "1.0.2", 514 | "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", 515 | "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==", 516 | "dev": true 517 | }, 518 | "is-binary-path": { 519 | "version": "2.1.0", 520 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 521 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 522 | "dev": true, 523 | "requires": { 524 | "binary-extensions": "^2.0.0" 525 | } 526 | }, 527 | "is-boolean-object": { 528 | "version": "1.1.1", 529 | "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", 530 | "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", 531 | "dev": true, 532 | "requires": { 533 | "call-bind": "^1.0.2" 534 | } 535 | }, 536 | "is-buffer": { 537 | "version": "2.0.5", 538 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", 539 | "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", 540 | "dev": true 541 | }, 542 | "is-callable": { 543 | "version": "1.2.3", 544 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", 545 | "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", 546 | "dev": true 547 | }, 548 | "is-date-object": { 549 | "version": "1.0.4", 550 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", 551 | "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==", 552 | "dev": true 553 | }, 554 | "is-extglob": { 555 | "version": "2.1.1", 556 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 557 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", 558 | "dev": true 559 | }, 560 | "is-fullwidth-code-point": { 561 | "version": "2.0.0", 562 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 563 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", 564 | "dev": true 565 | }, 566 | "is-glob": { 567 | "version": "4.0.1", 568 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", 569 | "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", 570 | "dev": true, 571 | "requires": { 572 | "is-extglob": "^2.1.1" 573 | } 574 | }, 575 | "is-negative-zero": { 576 | "version": "2.0.1", 577 | "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", 578 | "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", 579 | "dev": true 580 | }, 581 | "is-number": { 582 | "version": "7.0.0", 583 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 584 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 585 | "dev": true 586 | }, 587 | "is-number-object": { 588 | "version": "1.0.5", 589 | "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", 590 | "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==", 591 | "dev": true 592 | }, 593 | "is-regex": { 594 | "version": "1.1.3", 595 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", 596 | "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", 597 | "dev": true, 598 | "requires": { 599 | "call-bind": "^1.0.2", 600 | "has-symbols": "^1.0.2" 601 | } 602 | }, 603 | "is-string": { 604 | "version": "1.0.6", 605 | "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", 606 | "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==", 607 | "dev": true 608 | }, 609 | "is-symbol": { 610 | "version": "1.0.4", 611 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", 612 | "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", 613 | "dev": true, 614 | "requires": { 615 | "has-symbols": "^1.0.2" 616 | } 617 | }, 618 | "isarray": { 619 | "version": "1.0.0", 620 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 621 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 622 | "dev": true 623 | }, 624 | "isexe": { 625 | "version": "2.0.0", 626 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 627 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 628 | "dev": true 629 | }, 630 | "js-yaml": { 631 | "version": "3.13.1", 632 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", 633 | "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", 634 | "dev": true, 635 | "requires": { 636 | "argparse": "^1.0.7", 637 | "esprima": "^4.0.0" 638 | } 639 | }, 640 | "json5": { 641 | "version": "1.0.1", 642 | "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", 643 | "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", 644 | "dev": true, 645 | "requires": { 646 | "minimist": "^1.2.0" 647 | } 648 | }, 649 | "loader-utils": { 650 | "version": "1.4.0", 651 | "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", 652 | "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", 653 | "dev": true, 654 | "requires": { 655 | "big.js": "^5.2.2", 656 | "emojis-list": "^3.0.0", 657 | "json5": "^1.0.1" 658 | } 659 | }, 660 | "locate-path": { 661 | "version": "3.0.0", 662 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", 663 | "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", 664 | "dev": true, 665 | "requires": { 666 | "p-locate": "^3.0.0", 667 | "path-exists": "^3.0.0" 668 | } 669 | }, 670 | "lodash": { 671 | "version": "4.17.21", 672 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 673 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 674 | }, 675 | "log-symbols": { 676 | "version": "3.0.0", 677 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", 678 | "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", 679 | "dev": true, 680 | "requires": { 681 | "chalk": "^2.4.2" 682 | } 683 | }, 684 | "memory-fs": { 685 | "version": "0.5.0", 686 | "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", 687 | "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", 688 | "dev": true, 689 | "requires": { 690 | "errno": "^0.1.3", 691 | "readable-stream": "^2.0.1" 692 | } 693 | }, 694 | "micromatch": { 695 | "version": "4.0.4", 696 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", 697 | "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", 698 | "dev": true, 699 | "requires": { 700 | "braces": "^3.0.1", 701 | "picomatch": "^2.2.3" 702 | } 703 | }, 704 | "minimatch": { 705 | "version": "3.0.4", 706 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 707 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 708 | "dev": true, 709 | "requires": { 710 | "brace-expansion": "^1.1.7" 711 | } 712 | }, 713 | "minimist": { 714 | "version": "1.2.5", 715 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 716 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", 717 | "dev": true 718 | }, 719 | "mkdirp": { 720 | "version": "0.5.5", 721 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 722 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 723 | "dev": true, 724 | "requires": { 725 | "minimist": "^1.2.5" 726 | } 727 | }, 728 | "mocha": { 729 | "version": "7.2.0", 730 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz", 731 | "integrity": "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==", 732 | "dev": true, 733 | "requires": { 734 | "ansi-colors": "3.2.3", 735 | "browser-stdout": "1.3.1", 736 | "chokidar": "3.3.0", 737 | "debug": "3.2.6", 738 | "diff": "3.5.0", 739 | "escape-string-regexp": "1.0.5", 740 | "find-up": "3.0.0", 741 | "glob": "7.1.3", 742 | "growl": "1.10.5", 743 | "he": "1.2.0", 744 | "js-yaml": "3.13.1", 745 | "log-symbols": "3.0.0", 746 | "minimatch": "3.0.4", 747 | "mkdirp": "0.5.5", 748 | "ms": "2.1.1", 749 | "node-environment-flags": "1.0.6", 750 | "object.assign": "4.1.0", 751 | "strip-json-comments": "2.0.1", 752 | "supports-color": "6.0.0", 753 | "which": "1.3.1", 754 | "wide-align": "1.1.3", 755 | "yargs": "13.3.2", 756 | "yargs-parser": "13.1.2", 757 | "yargs-unparser": "1.6.0" 758 | } 759 | }, 760 | "ms": { 761 | "version": "2.1.1", 762 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 763 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", 764 | "dev": true 765 | }, 766 | "node-environment-flags": { 767 | "version": "1.0.6", 768 | "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", 769 | "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", 770 | "dev": true, 771 | "requires": { 772 | "object.getownpropertydescriptors": "^2.0.3", 773 | "semver": "^5.7.0" 774 | } 775 | }, 776 | "normalize-path": { 777 | "version": "3.0.0", 778 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 779 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 780 | "dev": true 781 | }, 782 | "object-inspect": { 783 | "version": "1.10.3", 784 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", 785 | "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==", 786 | "dev": true 787 | }, 788 | "object-keys": { 789 | "version": "1.1.1", 790 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", 791 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", 792 | "dev": true 793 | }, 794 | "object.assign": { 795 | "version": "4.1.0", 796 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", 797 | "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", 798 | "dev": true, 799 | "requires": { 800 | "define-properties": "^1.1.2", 801 | "function-bind": "^1.1.1", 802 | "has-symbols": "^1.0.0", 803 | "object-keys": "^1.0.11" 804 | } 805 | }, 806 | "object.getownpropertydescriptors": { 807 | "version": "2.1.2", 808 | "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", 809 | "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", 810 | "dev": true, 811 | "requires": { 812 | "call-bind": "^1.0.2", 813 | "define-properties": "^1.1.3", 814 | "es-abstract": "^1.18.0-next.2" 815 | } 816 | }, 817 | "once": { 818 | "version": "1.4.0", 819 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 820 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 821 | "dev": true, 822 | "requires": { 823 | "wrappy": "1" 824 | } 825 | }, 826 | "p-limit": { 827 | "version": "2.3.0", 828 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", 829 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", 830 | "dev": true, 831 | "requires": { 832 | "p-try": "^2.0.0" 833 | } 834 | }, 835 | "p-locate": { 836 | "version": "3.0.0", 837 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", 838 | "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", 839 | "dev": true, 840 | "requires": { 841 | "p-limit": "^2.0.0" 842 | } 843 | }, 844 | "p-try": { 845 | "version": "2.2.0", 846 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 847 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", 848 | "dev": true 849 | }, 850 | "path-exists": { 851 | "version": "3.0.0", 852 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", 853 | "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", 854 | "dev": true 855 | }, 856 | "path-is-absolute": { 857 | "version": "1.0.1", 858 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 859 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 860 | "dev": true 861 | }, 862 | "picomatch": { 863 | "version": "2.3.0", 864 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", 865 | "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", 866 | "dev": true 867 | }, 868 | "process-nextick-args": { 869 | "version": "2.0.1", 870 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 871 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", 872 | "dev": true 873 | }, 874 | "prr": { 875 | "version": "1.0.1", 876 | "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", 877 | "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", 878 | "dev": true 879 | }, 880 | "readable-stream": { 881 | "version": "2.3.7", 882 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 883 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 884 | "dev": true, 885 | "requires": { 886 | "core-util-is": "~1.0.0", 887 | "inherits": "~2.0.3", 888 | "isarray": "~1.0.0", 889 | "process-nextick-args": "~2.0.0", 890 | "safe-buffer": "~5.1.1", 891 | "string_decoder": "~1.1.1", 892 | "util-deprecate": "~1.0.1" 893 | } 894 | }, 895 | "readdirp": { 896 | "version": "3.2.0", 897 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", 898 | "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", 899 | "dev": true, 900 | "requires": { 901 | "picomatch": "^2.0.4" 902 | } 903 | }, 904 | "redis": { 905 | "version": "3.1.2", 906 | "resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz", 907 | "integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==", 908 | "requires": { 909 | "denque": "^1.5.0", 910 | "redis-commands": "^1.7.0", 911 | "redis-errors": "^1.2.0", 912 | "redis-parser": "^3.0.0" 913 | } 914 | }, 915 | "redis-commands": { 916 | "version": "1.7.0", 917 | "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", 918 | "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==" 919 | }, 920 | "redis-errors": { 921 | "version": "1.2.0", 922 | "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", 923 | "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=" 924 | }, 925 | "redis-parser": { 926 | "version": "3.0.0", 927 | "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", 928 | "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", 929 | "requires": { 930 | "redis-errors": "^1.0.0" 931 | } 932 | }, 933 | "require-directory": { 934 | "version": "2.1.1", 935 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 936 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", 937 | "dev": true 938 | }, 939 | "require-main-filename": { 940 | "version": "2.0.0", 941 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", 942 | "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", 943 | "dev": true 944 | }, 945 | "safe-buffer": { 946 | "version": "5.1.2", 947 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 948 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 949 | "dev": true 950 | }, 951 | "semver": { 952 | "version": "5.7.1", 953 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 954 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", 955 | "dev": true 956 | }, 957 | "set-blocking": { 958 | "version": "2.0.0", 959 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 960 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", 961 | "dev": true 962 | }, 963 | "should": { 964 | "version": "13.2.3", 965 | "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", 966 | "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", 967 | "dev": true, 968 | "requires": { 969 | "should-equal": "^2.0.0", 970 | "should-format": "^3.0.3", 971 | "should-type": "^1.4.0", 972 | "should-type-adaptors": "^1.0.1", 973 | "should-util": "^1.0.0" 974 | } 975 | }, 976 | "should-equal": { 977 | "version": "2.0.0", 978 | "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", 979 | "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", 980 | "dev": true, 981 | "requires": { 982 | "should-type": "^1.4.0" 983 | } 984 | }, 985 | "should-format": { 986 | "version": "3.0.3", 987 | "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", 988 | "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", 989 | "dev": true, 990 | "requires": { 991 | "should-type": "^1.3.0", 992 | "should-type-adaptors": "^1.0.1" 993 | } 994 | }, 995 | "should-type": { 996 | "version": "1.4.0", 997 | "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", 998 | "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", 999 | "dev": true 1000 | }, 1001 | "should-type-adaptors": { 1002 | "version": "1.1.0", 1003 | "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", 1004 | "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", 1005 | "dev": true, 1006 | "requires": { 1007 | "should-type": "^1.3.0", 1008 | "should-util": "^1.0.0" 1009 | } 1010 | }, 1011 | "should-util": { 1012 | "version": "1.0.1", 1013 | "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", 1014 | "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", 1015 | "dev": true 1016 | }, 1017 | "sprintf-js": { 1018 | "version": "1.0.3", 1019 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 1020 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 1021 | "dev": true 1022 | }, 1023 | "string-width": { 1024 | "version": "2.1.1", 1025 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", 1026 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", 1027 | "dev": true, 1028 | "requires": { 1029 | "is-fullwidth-code-point": "^2.0.0", 1030 | "strip-ansi": "^4.0.0" 1031 | } 1032 | }, 1033 | "string.prototype.trimend": { 1034 | "version": "1.0.4", 1035 | "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", 1036 | "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", 1037 | "dev": true, 1038 | "requires": { 1039 | "call-bind": "^1.0.2", 1040 | "define-properties": "^1.1.3" 1041 | } 1042 | }, 1043 | "string.prototype.trimstart": { 1044 | "version": "1.0.4", 1045 | "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", 1046 | "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", 1047 | "dev": true, 1048 | "requires": { 1049 | "call-bind": "^1.0.2", 1050 | "define-properties": "^1.1.3" 1051 | } 1052 | }, 1053 | "string_decoder": { 1054 | "version": "1.1.1", 1055 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 1056 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 1057 | "dev": true, 1058 | "requires": { 1059 | "safe-buffer": "~5.1.0" 1060 | } 1061 | }, 1062 | "strip-ansi": { 1063 | "version": "4.0.0", 1064 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 1065 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 1066 | "dev": true, 1067 | "requires": { 1068 | "ansi-regex": "^3.0.0" 1069 | } 1070 | }, 1071 | "strip-json-comments": { 1072 | "version": "2.0.1", 1073 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 1074 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", 1075 | "dev": true 1076 | }, 1077 | "supports-color": { 1078 | "version": "6.0.0", 1079 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", 1080 | "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", 1081 | "dev": true, 1082 | "requires": { 1083 | "has-flag": "^3.0.0" 1084 | } 1085 | }, 1086 | "tapable": { 1087 | "version": "1.1.3", 1088 | "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", 1089 | "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", 1090 | "dev": true 1091 | }, 1092 | "to-regex-range": { 1093 | "version": "5.0.1", 1094 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1095 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1096 | "dev": true, 1097 | "requires": { 1098 | "is-number": "^7.0.0" 1099 | } 1100 | }, 1101 | "ts-loader": { 1102 | "version": "6.2.2", 1103 | "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-6.2.2.tgz", 1104 | "integrity": "sha512-HDo5kXZCBml3EUPcc7RlZOV/JGlLHwppTLEHb3SHnr5V7NXD4klMEkrhJe5wgRbaWsSXi+Y1SIBN/K9B6zWGWQ==", 1105 | "dev": true, 1106 | "requires": { 1107 | "chalk": "^2.3.0", 1108 | "enhanced-resolve": "^4.0.0", 1109 | "loader-utils": "^1.0.2", 1110 | "micromatch": "^4.0.0", 1111 | "semver": "^6.0.0" 1112 | }, 1113 | "dependencies": { 1114 | "semver": { 1115 | "version": "6.3.0", 1116 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 1117 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", 1118 | "dev": true 1119 | } 1120 | } 1121 | }, 1122 | "typescript": { 1123 | "version": "4.3.2", 1124 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", 1125 | "integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==", 1126 | "dev": true 1127 | }, 1128 | "unbox-primitive": { 1129 | "version": "1.0.1", 1130 | "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", 1131 | "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", 1132 | "dev": true, 1133 | "requires": { 1134 | "function-bind": "^1.1.1", 1135 | "has-bigints": "^1.0.1", 1136 | "has-symbols": "^1.0.2", 1137 | "which-boxed-primitive": "^1.0.2" 1138 | } 1139 | }, 1140 | "util-deprecate": { 1141 | "version": "1.0.2", 1142 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1143 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", 1144 | "dev": true 1145 | }, 1146 | "which": { 1147 | "version": "1.3.1", 1148 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 1149 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 1150 | "dev": true, 1151 | "requires": { 1152 | "isexe": "^2.0.0" 1153 | } 1154 | }, 1155 | "which-boxed-primitive": { 1156 | "version": "1.0.2", 1157 | "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", 1158 | "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", 1159 | "dev": true, 1160 | "requires": { 1161 | "is-bigint": "^1.0.1", 1162 | "is-boolean-object": "^1.1.0", 1163 | "is-number-object": "^1.0.4", 1164 | "is-string": "^1.0.5", 1165 | "is-symbol": "^1.0.3" 1166 | } 1167 | }, 1168 | "which-module": { 1169 | "version": "2.0.0", 1170 | "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", 1171 | "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", 1172 | "dev": true 1173 | }, 1174 | "wide-align": { 1175 | "version": "1.1.3", 1176 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", 1177 | "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", 1178 | "dev": true, 1179 | "requires": { 1180 | "string-width": "^1.0.2 || 2" 1181 | } 1182 | }, 1183 | "wrap-ansi": { 1184 | "version": "5.1.0", 1185 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", 1186 | "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", 1187 | "dev": true, 1188 | "requires": { 1189 | "ansi-styles": "^3.2.0", 1190 | "string-width": "^3.0.0", 1191 | "strip-ansi": "^5.0.0" 1192 | }, 1193 | "dependencies": { 1194 | "ansi-regex": { 1195 | "version": "4.1.0", 1196 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 1197 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", 1198 | "dev": true 1199 | }, 1200 | "string-width": { 1201 | "version": "3.1.0", 1202 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 1203 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 1204 | "dev": true, 1205 | "requires": { 1206 | "emoji-regex": "^7.0.1", 1207 | "is-fullwidth-code-point": "^2.0.0", 1208 | "strip-ansi": "^5.1.0" 1209 | } 1210 | }, 1211 | "strip-ansi": { 1212 | "version": "5.2.0", 1213 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 1214 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 1215 | "dev": true, 1216 | "requires": { 1217 | "ansi-regex": "^4.1.0" 1218 | } 1219 | } 1220 | } 1221 | }, 1222 | "wrappy": { 1223 | "version": "1.0.2", 1224 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1225 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1226 | "dev": true 1227 | }, 1228 | "y18n": { 1229 | "version": "4.0.3", 1230 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", 1231 | "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", 1232 | "dev": true 1233 | }, 1234 | "yargs": { 1235 | "version": "13.3.2", 1236 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", 1237 | "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", 1238 | "dev": true, 1239 | "requires": { 1240 | "cliui": "^5.0.0", 1241 | "find-up": "^3.0.0", 1242 | "get-caller-file": "^2.0.1", 1243 | "require-directory": "^2.1.1", 1244 | "require-main-filename": "^2.0.0", 1245 | "set-blocking": "^2.0.0", 1246 | "string-width": "^3.0.0", 1247 | "which-module": "^2.0.0", 1248 | "y18n": "^4.0.0", 1249 | "yargs-parser": "^13.1.2" 1250 | }, 1251 | "dependencies": { 1252 | "ansi-regex": { 1253 | "version": "4.1.0", 1254 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 1255 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", 1256 | "dev": true 1257 | }, 1258 | "string-width": { 1259 | "version": "3.1.0", 1260 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 1261 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 1262 | "dev": true, 1263 | "requires": { 1264 | "emoji-regex": "^7.0.1", 1265 | "is-fullwidth-code-point": "^2.0.0", 1266 | "strip-ansi": "^5.1.0" 1267 | } 1268 | }, 1269 | "strip-ansi": { 1270 | "version": "5.2.0", 1271 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 1272 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 1273 | "dev": true, 1274 | "requires": { 1275 | "ansi-regex": "^4.1.0" 1276 | } 1277 | } 1278 | } 1279 | }, 1280 | "yargs-parser": { 1281 | "version": "13.1.2", 1282 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", 1283 | "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", 1284 | "dev": true, 1285 | "requires": { 1286 | "camelcase": "^5.0.0", 1287 | "decamelize": "^1.2.0" 1288 | } 1289 | }, 1290 | "yargs-unparser": { 1291 | "version": "1.6.0", 1292 | "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", 1293 | "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", 1294 | "dev": true, 1295 | "requires": { 1296 | "flat": "^4.1.0", 1297 | "lodash": "^4.17.15", 1298 | "yargs": "^13.3.0" 1299 | } 1300 | } 1301 | } 1302 | } 1303 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rsmq", 3 | "description": "A really simple message queue based on Redis", 4 | "version": "0.12.4", 5 | "license": "MIT", 6 | "author": "P. Liess ", 7 | "engines": { 8 | "node": "> 6.0.0" 9 | }, 10 | "scripts": { 11 | "build": "tsc", 12 | "watch": "tsc -w", 13 | "test": "mocha ./test/test.js" 14 | }, 15 | "dependencies": { 16 | "lodash": "^4.17.21", 17 | "redis": "^3.1.2" 18 | }, 19 | "devDependencies": { 20 | "@types/redis": "^2.8.29", 21 | "async": "^3.2.0", 22 | "coffeescript": "^2.5.1", 23 | "mocha": "^7.0.1", 24 | "should": "^13.2.3", 25 | "ts-loader": "^6.2.1", 26 | "typescript": "4.3.2" 27 | }, 28 | "keywords": [ 29 | "queue", 30 | "messagequeue", 31 | "jobs", 32 | "message-queue", 33 | "redis" 34 | ], 35 | "repository": { 36 | "type": "git", 37 | "url": "http://github.com/smrchy/rsmq.git" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/smrchy/rsmq/issues" 41 | }, 42 | "main": "./index.js", 43 | "types": "./index.d.ts", 44 | "files": [ 45 | "index.js", 46 | "index.d.ts" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --exit 2 | --bail 3 | --slow 10 4 | --reporter spec -------------------------------------------------------------------------------- /test/test.coffee: -------------------------------------------------------------------------------- 1 | _ = require "lodash" 2 | should = require "should" 3 | async = require "async" 4 | RedisSMQ = require "../index" 5 | 6 | RedisInst = require "redis" 7 | redis = RedisInst.createClient() 8 | redissub = RedisInst.createClient() 9 | redissub.subscribe("rsmq:rt:test1") 10 | Q1LENGTH = 0 11 | redissub.on "message", (channel, depth) -> 12 | Q1LENGTH = Number(depth) 13 | return 14 | 15 | describe 'Redis-Simple-Message-Queue Test', -> 16 | rsmq = null 17 | rsmq2 = null 18 | queue1 = 19 | name: "test1" 20 | queue2 = 21 | name: "test2" 22 | queue3 = 23 | name: "test3promises" 24 | m1: "Hello" 25 | m2: "World" 26 | 27 | q1m1 = null 28 | q1m2 = null 29 | q1m3 = null 30 | q2m2 = null 31 | q2msgs = {} 32 | 33 | looong_string = -> 34 | o = "" 35 | while o.length < 66000 36 | o = o + 'A very long Message...' 37 | return o 38 | 39 | before (done) -> 40 | done() 41 | return 42 | 43 | after (done) -> 44 | console.log("Removing Queues") 45 | # Kill all queues 46 | rsmq.deleteQueue {qname: queue1.name}, (err) -> 47 | return 48 | 49 | rsmq.deleteQueue {qname: queue2.name}, (err) -> 50 | return 51 | @timeout(100) 52 | rsmq.quit (err, resp) -> 53 | console.log("RSMQ quit:", resp) 54 | done() 55 | return 56 | return 57 | 58 | it 'get a RedisSMQ instance', (done) -> 59 | rsmq = new RedisSMQ({realtime: true}) 60 | rsmq.should.be.an.instanceOf RedisSMQ 61 | done() 62 | return 63 | 64 | it 'use an existing Redis Client', (done) -> 65 | rsmq2 = new RedisSMQ({client: redis}) 66 | rsmq2.should.be.an.instanceOf RedisSMQ 67 | done() 68 | return 69 | 70 | it 'should delete all leftover queues', (done) -> 71 | rsmq.deleteQueue {qname: queue1.name}, (err) -> 72 | return 73 | 74 | rsmq.deleteQueue {qname: queue2.name}, (err) -> 75 | return 76 | 77 | rsmq.deleteQueue {qname: queue3.name}, (err) -> 78 | return 79 | 80 | setTimeout(done, 100) 81 | return 82 | 83 | describe 'Promise Api', -> 84 | it 'should create a queue', () -> rsmq.createQueueAsync({qname: queue3.name, vt: 0, maxsize: -1}) 85 | it 'should send a message', () -> rsmq.sendMessageAsync({qname: queue3.name, message: queue3.m1}) 86 | it 'should send another message', () -> rsmq.sendMessageAsync({qname: queue3.name, message: queue3.m2}) 87 | it 'should check the maxsize of the queue', () -> rsmq.getQueueAttributesAsync({qname: queue3.name}).then((resp) -> 88 | resp.maxsize.should.equal(-1) 89 | return 90 | ) 91 | it 'should receive a message', () -> 92 | return rsmq.receiveMessageAsync({qname: queue3.name, vt: 2}).then((resp) -> 93 | resp.message.should.equal(queue3.m1) 94 | return 95 | ) 96 | it 'should receive another message', () -> 97 | return rsmq.receiveMessageAsync({qname: queue3.name, vt: 1}).then((resp) -> 98 | resp.message.should.equal(queue3.m2) 99 | return 100 | ) 101 | it 'Should fail: receive another message - no availabe message', () -> 102 | return rsmq.receiveMessageAsync({qname: queue3.name, vt: 1}).then((resp) -> 103 | should.not.exist(resp.id) 104 | return 105 | ) 106 | it 'wait 1010ms', (done) -> setTimeout(done, 1010) 107 | it 'should receive another message', () -> 108 | return rsmq.receiveMessageAsync({qname: queue3.name, vt: 3}).then((resp) -> 109 | resp.message.should.equal(queue3.m2) 110 | return 111 | ) 112 | it 'wait 1010ms', (done) -> setTimeout(done, 1010) 113 | it 'should receive another message', () -> 114 | return rsmq.receiveMessageAsync({qname: queue3.name, vt: 3}).then((resp) -> 115 | resp.message.should.equal(queue3.m1) 116 | return 117 | ) 118 | it 'should delete the created queue', () -> rsmq.deleteQueueAsync({qname: queue3.name}) 119 | return 120 | 121 | describe 'Queues', -> 122 | 123 | it 'Should fail: Create a new queue with invalid characters in name', (done) -> 124 | rsmq.createQueue {qname:"should throw"}, (err, resp) -> 125 | err.message.should.equal("Invalid qname format") 126 | done() 127 | return 128 | return 129 | it 'Should fail: Create a new queue with name longer 160 chars', (done) -> 130 | rsmq.createQueue {qname:"name01234567890123456789012345678901234567890123456789012345678901234567890123456789name01234567890123456789012345678901234567890123456789012345678901234567890123456789"}, (err, resp) -> 131 | err.message.should.equal("Invalid qname format") 132 | done() 133 | return 134 | return 135 | it 'Should fail: Create a new queue with negative vt', (done) -> 136 | rsmq.createQueue {qname: queue1.name, vt: -20}, (err, resp) -> 137 | err.message.should.equal("vt must be between 0 and 9999999") 138 | done() 139 | return 140 | return 141 | it 'Should fail: Create a new queue with negative vt - using createQueueAsync', () -> 142 | return rsmq.createQueueAsync({qname: queue1.name, vt: -20}).should.be.rejectedWith(Error, { message: "vt must be between 0 and 9999999" }) 143 | 144 | it 'Should fail: Create a new queue with non numeric vt', (done) -> 145 | rsmq.createQueue {qname: queue1.name, vt: "not_a_number"}, (err, resp) -> 146 | err.message.should.equal("vt must be between 0 and 9999999") 147 | done() 148 | return 149 | return 150 | it 'Should fail: Create a new queue with non numeric vt', () -> 151 | return rsmq.createQueueAsync({qname: queue1.name, vt: "not_a_number"}).should.be.rejectedWith(Error, { message: "vt must be between 0 and 9999999" }) 152 | it 'Should fail: Create a new queue with vt too high', (done) -> 153 | rsmq.createQueue {qname: queue1.name, vt: 10000000}, (err, resp) -> 154 | err.message.should.equal("vt must be between 0 and 9999999") 155 | done() 156 | return 157 | return 158 | it 'Should fail: Create a new queue with negative delay', (done) -> 159 | rsmq.createQueue {qname: queue1.name, delay: -20}, (err, resp) -> 160 | err.message.should.equal("delay must be between 0 and 9999999") 161 | done() 162 | return 163 | return 164 | it 'Should fail: Create a new queue with non numeric delay', (done) -> 165 | rsmq.createQueue {qname: queue1.name, delay: "not_a_number"}, (err, resp) -> 166 | err.message.should.equal("delay must be between 0 and 9999999") 167 | done() 168 | return 169 | return 170 | it 'Should fail: Create a new queue with delay too high', (done) -> 171 | rsmq.createQueue {qname: queue1.name, delay: 10000000}, (err, resp) -> 172 | err.message.should.equal("delay must be between 0 and 9999999") 173 | done() 174 | return 175 | return 176 | it 'Should fail: Create a new queue with negative maxsize', (done) -> 177 | rsmq.createQueue {qname: queue1.name, maxsize: -20}, (err, resp) -> 178 | err.message.should.equal("maxsize must be between 1024 and 65536") 179 | done() 180 | return 181 | return 182 | it 'Should fail: Create a new queue with non numeric maxsize', (done) -> 183 | rsmq.createQueue {qname: queue1.name, maxsize: "not_a_number"}, (err, resp) -> 184 | err.message.should.equal("maxsize must be between 1024 and 65536") 185 | done() 186 | return 187 | return 188 | it 'Should fail: Create a new queue with maxsize too high', (done) -> 189 | rsmq.createQueue {qname: queue1.name, maxsize: 66000}, (err, resp) -> 190 | err.message.should.equal("maxsize must be between 1024 and 65536") 191 | done() 192 | return 193 | return 194 | it 'Should fail: Create a new queue with maxsize too low', (done) -> 195 | rsmq.createQueue {qname: queue1.name, maxsize: 900}, (err, resp) -> 196 | err.message.should.equal("maxsize must be between 1024 and 65536") 197 | done() 198 | return 199 | return 200 | it 'Should fail: Create a new queue with maxsize `-2`', (done) -> 201 | rsmq.createQueue {qname: queue1.name, maxsize: -2}, (err, resp) -> 202 | err.message.should.equal("maxsize must be between 1024 and 65536") 203 | done() 204 | return 205 | return 206 | 207 | it 'ListQueues: Should return empty array', (done) -> 208 | rsmq.listQueues (err, resp) -> 209 | should.not.exist(err) 210 | resp.length.should.equal(0) 211 | done() 212 | return 213 | return 214 | 215 | it 'Create a new queue: queue1', (done) -> 216 | rsmq.createQueue {qname: queue1.name}, (err, resp) -> 217 | should.not.exist(err) 218 | resp.should.equal(1) 219 | done() 220 | return 221 | return 222 | 223 | it 'Should fail: Create the same queue again', (done) -> 224 | rsmq.createQueue {qname: queue1.name}, (err, resp) -> 225 | err.message.should.equal("Queue exists") 226 | done() 227 | return 228 | return 229 | 230 | it 'ListQueues: Should return array with one element', (done) -> 231 | rsmq.listQueues (err, resp) -> 232 | should.not.exist(err) 233 | resp.length.should.equal(1) 234 | resp.should.containEql( queue1.name) 235 | done() 236 | return 237 | return 238 | 239 | 240 | it 'Create a new queue: queue2', (done) -> 241 | rsmq.createQueue {qname: queue2.name, maxsize:2048}, (err, resp) -> 242 | should.not.exist(err) 243 | resp.should.equal(1) 244 | done() 245 | return 246 | return 247 | 248 | 249 | it 'ListQueues: Should return array with two elements', (done) -> 250 | rsmq.listQueues (err, resp) -> 251 | should.not.exist(err) 252 | resp.length.should.equal(2) 253 | resp.should.containEql(queue1.name) 254 | resp.should.containEql(queue2.name) 255 | done() 256 | return 257 | return 258 | 259 | it 'Should succeed: GetQueueAttributes of queue 1', (done) -> 260 | rsmq.getQueueAttributes {qname: queue1.name}, (err, resp) -> 261 | should.not.exist(err) 262 | resp.msgs.should.equal(0) 263 | queue1.modified = resp.modified 264 | done() 265 | return 266 | return 267 | 268 | it 'Should fail: GetQueueAttributes of bogus queue', (done) -> 269 | rsmq.getQueueAttributes {qname:"sdfsdfsdf"}, (err, resp) -> 270 | err.message.should.equal("Queue not found") 271 | done() 272 | return 273 | return 274 | 275 | it 'Should fail: setQueueAttributes of bogus queue with no supplied attributes', (done) -> 276 | rsmq.setQueueAttributes {qname:"kjdsfh3h"}, (err, resp) -> 277 | err.message.should.equal("No attribute was supplied") 278 | done() 279 | return 280 | return 281 | 282 | it 'Should fail: setQueueAttributes of bogus queue with supplied attributes', (done) -> 283 | rsmq.setQueueAttributes {qname:"kjdsfh3h",vt: 1000}, (err, resp) -> 284 | err.message.should.equal("Queue not found") 285 | done() 286 | return 287 | return 288 | 289 | it 'setQueueAttributes: Should return the queue with a new vt attribute', (done) -> 290 | rsmq.setQueueAttributes {qname: queue1.name, vt: 1234}, (err, resp) -> 291 | resp.vt.should.equal(1234) 292 | resp.delay.should.equal(0) 293 | resp.maxsize.should.equal(65536) 294 | done() 295 | return 296 | return 297 | 298 | it 'setQueueAttributes: Should return the queue with a new delay attribute', (done) -> 299 | @timeout(2000) 300 | setTimeout -> 301 | rsmq.setQueueAttributes {qname: queue1.name, delay: 7}, (err, resp) -> 302 | resp.vt.should.equal(1234) 303 | resp.delay.should.equal(7) 304 | resp.maxsize.should.equal(65536) 305 | resp.modified.should.be.above(queue1.modified) 306 | done() 307 | return 308 | return 309 | , 1100 310 | return 311 | 312 | it 'setQueueAttributes: Should return the queue with an umlimited maxsize', (done) -> 313 | rsmq.setQueueAttributes {qname: queue1.name, maxsize: -1}, (err, resp) -> 314 | resp.vt.should.equal(1234) 315 | resp.delay.should.equal(7) 316 | resp.maxsize.should.equal(-1) 317 | done() 318 | return 319 | return 320 | 321 | it 'setQueueAttributes: Should return the queue with a new maxsize attribute', (done) -> 322 | rsmq.setQueueAttributes {qname: queue1.name, maxsize: 2048}, (err, resp) -> 323 | resp.vt.should.equal(1234) 324 | resp.delay.should.equal(7) 325 | resp.maxsize.should.equal(2048) 326 | done() 327 | return 328 | return 329 | 330 | it 'setQueueAttributes: Should return the queue with a new attribute', (done) -> 331 | rsmq.setQueueAttributes {qname: queue1.name, maxsize: 65536, vt: 30, delay: 0}, (err, resp) -> 332 | resp.vt.should.equal(30) 333 | resp.delay.should.equal(0) 334 | resp.maxsize.should.equal(65536) 335 | done() 336 | return 337 | return 338 | 339 | it 'Should fail:setQueueAttributes: Should not accept too small maxsize', (done) -> 340 | rsmq.setQueueAttributes {qname: queue1.name, maxsize: 50}, (err, resp) -> 341 | err.message.should.equal("maxsize must be between 1024 and 65536") 342 | done() 343 | return 344 | return 345 | 346 | it 'Should fail:setQueueAttributes: Should not accept negative value', (done) -> 347 | rsmq.setQueueAttributes {qname: queue1.name, vt: -5}, (err, resp) -> 348 | err.message.should.equal("vt must be between 0 and 9999999") 349 | done() 350 | return 351 | return 352 | 353 | return 354 | 355 | describe 'Messages', -> 356 | it 'Should fail: Send a message to non-existing queue', (done) -> 357 | rsmq.sendMessage {qname:"rtlbrmpft", message:"foo"}, (err, resp) -> 358 | err.message.should.equal("Queue not found") 359 | done() 360 | return 361 | return 362 | it 'Should fail: Send a message without any parameters', (done) -> 363 | rsmq.sendMessage {}, (err, resp) -> 364 | err.message.should.equal("No qname supplied") 365 | done() 366 | return 367 | return 368 | it 'Should fail: Send a message without a message key', (done) -> 369 | rsmq.sendMessage {qname: queue1.name, messXage:"Hello"}, (err, resp) -> 370 | err.message.should.equal("Message must be a string") 371 | done() 372 | return 373 | return 374 | it 'Should fail: Send a message with message being a number', (done) -> 375 | rsmq.sendMessage {qname: queue1.name, message:123}, (err, resp) -> 376 | err.message.should.equal("Message must be a string") 377 | done() 378 | return 379 | return 380 | 381 | # TODO: Try to send a loooong msg 382 | 383 | it 'Send message 1 with existing Redis instance', (done) -> 384 | rsmq2.sendMessage {qname: queue1.name, message:"Hello"}, (err, resp) -> 385 | should.not.exist(err) 386 | q1m1 = 387 | id: resp 388 | message: "Hello" 389 | done() 390 | return 391 | return 392 | 393 | # Send 1000 msgs to q2 so we can delay sending of msg 2 to q1 394 | 395 | it 'Send 1000 messages to queue2: succeed', (done) -> 396 | pq = [] 397 | for i in [0...1000] 398 | pq.push({qname: queue2.name, message: "test message number:" + i}) 399 | async.map pq, rsmq.sendMessage, (err, resp) -> 400 | for e in resp 401 | q2msgs[e] = 1 402 | e.length.should.equal(32) 403 | _.keys(q2msgs).length.should.equal(1000) 404 | done() 405 | return 406 | return 407 | 408 | it 'Send message 2', (done) -> 409 | rsmq.sendMessage {qname: queue1.name, message:"World"}, (err, resp) -> 410 | should.not.exist(err) 411 | q1m2 = 412 | id: resp 413 | message: "World" 414 | done() 415 | return 416 | return 417 | 418 | 419 | it 'Receive a message. Should return message 1', (done) -> 420 | rsmq2.receiveMessage {qname: queue1.name}, (err, resp) -> 421 | resp.id.should.equal(q1m1.id) 422 | done() 423 | return 424 | return 425 | 426 | it 'Receive a message. Should return message 2', (done) -> 427 | rsmq.receiveMessage {qname: queue1.name}, (err, resp) -> 428 | resp.id.should.equal(q1m2.id) 429 | done() 430 | return 431 | return 432 | 433 | 434 | it 'Check queue properties. Should have 2 msgs', (done) -> 435 | rsmq.getQueueAttributes {qname: queue1.name}, (err, resp) -> 436 | resp.msgs.should.equal(2) 437 | resp.hiddenmsgs.should.equal(2) 438 | done() 439 | return 440 | return 441 | 442 | it 'Send message 3', (done) -> 443 | rsmq.sendMessage {qname: queue1.name, message:"Booo!!"}, (err, resp) -> 444 | should.not.exist(err) 445 | q1m3= 446 | id: resp 447 | message: "Booo!!" 448 | done() 449 | return 450 | return 451 | 452 | it 'Check queue properties. Should have 3 msgs', (done) -> 453 | rsmq.getQueueAttributes {qname: queue1.name}, (err, resp) -> 454 | resp.msgs.should.equal(3) 455 | resp.totalrecv.should.equal(2) 456 | done() 457 | return 458 | return 459 | 460 | it 'Pop a message. Should return message 3 and delete it', (done) -> 461 | rsmq.popMessage {qname: queue1.name}, (err, resp) -> 462 | resp.id.should.equal(q1m3.id) 463 | done() 464 | return 465 | return 466 | 467 | it 'Check queue properties. Should have 2 msgs', (done) -> 468 | rsmq.getQueueAttributes {qname: queue1.name}, (err, resp) -> 469 | resp.msgs.should.equal(2) 470 | resp.totalrecv.should.equal(3) 471 | done() 472 | return 473 | return 474 | 475 | it 'Pop a message. Should not return a message', (done) -> 476 | rsmq.popMessage {qname: queue1.name}, (err, resp) -> 477 | should.not.exist(resp.id) 478 | done() 479 | return 480 | return 481 | 482 | it 'Should fail. Set the visibility of a non existing message', (done) -> 483 | rsmq.changeMessageVisibility {qname: queue1.name, id:"abcdefghij0123456789abcdefghij01", vt:10}, (err, resp) -> 484 | resp.should.equal(0) 485 | done() 486 | return 487 | return 488 | 489 | it 'Set new visibility timeout of message 2 to 10s', (done) -> 490 | rsmq.changeMessageVisibility {qname: queue1.name, id:q1m2.id, vt: 10}, (err, resp) -> 491 | resp.should.equal(1) 492 | done() 493 | return 494 | return 495 | 496 | it 'Receive a message. Should return nothing', (done) -> 497 | rsmq.receiveMessage {qname: queue1.name}, (err, resp) -> 498 | should.not.exist(resp.id) 499 | done() 500 | return 501 | return 502 | 503 | it 'Set new visibility timeout of message 2 to 0s', (done) -> 504 | rsmq.changeMessageVisibility {qname: queue1.name, id:q1m2.id, vt: 0}, (err, resp) -> 505 | resp.should.equal(1) 506 | done() 507 | return 508 | return 509 | 510 | it 'Receive a message. Should return message 2', (done) -> 511 | rsmq.receiveMessage {qname: queue1.name}, (err, resp) -> 512 | resp.id.should.equal(q1m2.id) 513 | done() 514 | return 515 | return 516 | 517 | it 'Receive a message. Should return nothing', (done) -> 518 | rsmq.receiveMessage {qname: queue1.name}, (err, resp) -> 519 | should.not.exist(resp.id) 520 | done() 521 | return 522 | return 523 | 524 | it 'Should fail: Delete a message without supplying an id', (done) -> 525 | rsmq.deleteMessage {qname: queue1.name}, (err, resp) -> 526 | err.message.should.equal("No id supplied") 527 | done() 528 | return 529 | return 530 | 531 | it 'Should fail: Delete a message with invalid id', (done) -> 532 | rsmq.deleteMessage {qname: queue1.name, id:"sdafsdf"}, (err, resp) -> 533 | err.message.should.equal("Invalid id format") 534 | done() 535 | return 536 | return 537 | 538 | it 'Delete message 1. Should return 1', (done) -> 539 | rsmq.deleteMessage {qname: queue1.name, id: q1m1.id}, (err, resp) -> 540 | resp.should.equal(1) 541 | done() 542 | return 543 | return 544 | 545 | it 'Delete message 1 again. Should return 0', (done) -> 546 | rsmq.deleteMessage {qname: queue1.name, id: q1m1.id}, (err, resp) -> 547 | resp.should.equal(0) 548 | done() 549 | return 550 | return 551 | 552 | it 'Set new visibility timeout of message 1. Should return 0.', (done) -> 553 | rsmq.changeMessageVisibility {qname: queue1.name, id:q1m1.id, vt: 10}, (err, resp) -> 554 | resp.should.equal(0) 555 | done() 556 | return 557 | return 558 | 559 | it 'Should fail: Send a message that is too long', (done) -> 560 | text = JSON.stringify([0..15000]) 561 | rsmq.sendMessage {qname: queue1.name, message:text}, (err, resp) -> 562 | should.not.exist(resp) 563 | err.message.should.equal("Message too long") 564 | done() 565 | return 566 | return 567 | 568 | it 'Receive 1000 messages from queue2 and delete 500 (those where number is even)', (done) -> 569 | pq = [] 570 | # we keep vt = 0 so we can query them again quickly 571 | for i in [0...1000] 572 | pq.push({qname: queue2.name, vt:0}) 573 | async.map pq, rsmq.receiveMessage, (err, resp) -> 574 | dq = [] 575 | for e in resp when not (e.message.split(":")[1] % 2) 576 | dq.push({qname: queue2.name, id:e.id}) 577 | delete q2msgs[e.id] 578 | async.map dq, rsmq.deleteMessage, (err, resp) -> 579 | for e in resp 580 | e.should.equal(1) 581 | done() 582 | return 583 | return 584 | return 585 | 586 | it 'GetQueueAttributes: Should return queue attributes', (done) -> 587 | rsmq.getQueueAttributes {qname: queue2.name}, (err, resp) -> 588 | should.not.exist(err) 589 | resp.msgs.should.equal(500) 590 | done() 591 | return 592 | return 593 | 594 | it 'Receive 500 messages from queue2 and delete them', (done) -> 595 | pq = [] 596 | # we keep vt = 0 so we can query them again quickly 597 | for i in [0...500] 598 | pq.push({qname: queue2.name, vt:0}) 599 | async.map pq, rsmq.receiveMessage, (err, resp) -> 600 | dq = [] 601 | for e in resp when e.message.split(":")[1] % 2 602 | dq.push({qname: queue2.name, id:e.id}) 603 | delete q2msgs[e.id] 604 | async.map dq, rsmq.deleteMessage, (err, resp) -> 605 | for e in resp 606 | e.should.equal(1) 607 | done() 608 | 609 | # q2msgs should be empty 610 | _.keys(q2msgs).length.should.equal(0) 611 | return 612 | return 613 | return 614 | 615 | it 'Receive a message from queue2. Should return {}', (done) -> 616 | rsmq.receiveMessage {qname: queue2.name}, (err, resp) -> 617 | should.not.exist(resp.id) 618 | done() 619 | return 620 | return 621 | 622 | 623 | it 'GetQueueAttributes: Should return queue attributes', (done) -> 624 | rsmq.getQueueAttributes {qname: queue2.name}, (err, resp) -> 625 | should.not.exist(err) 626 | resp.totalrecv.should.equal(1500) 627 | resp.totalsent.should.equal(1000) 628 | resp.msgs.should.equal(0) 629 | done() 630 | return 631 | return 632 | 633 | it 'setQueueAttributes: Should return the queue2 with an umlimited maxsize', (done) -> 634 | rsmq.setQueueAttributes {qname: queue2.name , delay: 0, vt: 30, maxsize: -1}, (err, resp) -> 635 | resp.vt.should.equal(30) 636 | resp.delay.should.equal(0) 637 | resp.maxsize.should.equal(-1) 638 | done() 639 | return 640 | return 641 | 642 | it 'Send/Recevice a longer than 64k msg to test unlimited functionality', (done) -> 643 | longmsg = looong_string() 644 | rsmq.sendMessage {qname: queue2.name, message: longmsg}, (err, resp1) -> 645 | should.not.exist(err) 646 | rsmq.receiveMessage {qname: queue2.name}, (err, resp2) -> 647 | should.not.exist(err) 648 | resp2.message.should.equal(longmsg) 649 | resp2.id.should.equal(resp1) 650 | done() 651 | return 652 | return 653 | return 654 | return 655 | 656 | describe 'Realtime Pub/Sub notifications', -> 657 | it 'Send another message to queue1', (done) -> 658 | rsmq.sendMessage {qname: queue1.name, message:"Another World"}, (err, resp) -> 659 | should.not.exist(err) 660 | done() 661 | return 662 | return 663 | 664 | it 'wait 100ms', (done) -> setTimeout(done, 100) 665 | 666 | it 'check queue1 length. Should be 2', (done) -> 667 | Q1LENGTH.should.equal(2) 668 | done() 669 | return 670 | 671 | it 'Send another message to queue1', (done) -> 672 | rsmq.sendMessage {qname: queue1.name, message:"Another World"}, (err, resp) -> 673 | should.not.exist(err) 674 | done() 675 | return 676 | return 677 | 678 | it 'wait 100ms', (done) -> setTimeout(done, 100) 679 | 680 | it 'check queue1 length. Should be 3', (done) -> 681 | Q1LENGTH.should.equal(3) 682 | done() 683 | return 684 | return 685 | return 686 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 2.5.1 2 | var Q1LENGTH, RedisInst, RedisSMQ, _, async, redis, redissub, should; 3 | 4 | _ = require("lodash"); 5 | 6 | should = require("should"); 7 | 8 | async = require("async"); 9 | 10 | RedisSMQ = require("../index"); 11 | 12 | RedisInst = require("redis"); 13 | 14 | redis = RedisInst.createClient(); 15 | 16 | redissub = RedisInst.createClient(); 17 | 18 | redissub.subscribe("rsmq:rt:test1"); 19 | 20 | Q1LENGTH = 0; 21 | 22 | redissub.on("message", function(channel, depth) { 23 | Q1LENGTH = Number(depth); 24 | }); 25 | 26 | describe('Redis-Simple-Message-Queue Test', function() { 27 | var looong_string, q1m1, q1m2, q1m3, q2m2, q2msgs, queue1, queue2, queue3, rsmq, rsmq2; 28 | rsmq = null; 29 | rsmq2 = null; 30 | queue1 = { 31 | name: "test1" 32 | }; 33 | queue2 = { 34 | name: "test2" 35 | }; 36 | queue3 = { 37 | name: "test3promises", 38 | m1: "Hello", 39 | m2: "World" 40 | }; 41 | q1m1 = null; 42 | q1m2 = null; 43 | q1m3 = null; 44 | q2m2 = null; 45 | q2msgs = {}; 46 | looong_string = function() { 47 | var o; 48 | o = ""; 49 | while (o.length < 66000) { 50 | o = o + 'A very long Message...'; 51 | } 52 | return o; 53 | }; 54 | before(function(done) { 55 | done(); 56 | }); 57 | after(function(done) { 58 | console.log("Removing Queues"); 59 | // Kill all queues 60 | rsmq.deleteQueue({ 61 | qname: queue1.name 62 | }, function(err) {}); 63 | rsmq.deleteQueue({ 64 | qname: queue2.name 65 | }, function(err) {}); 66 | this.timeout(100); 67 | rsmq.quit(function(err, resp) { 68 | console.log("RSMQ quit:", resp); 69 | done(); 70 | }); 71 | }); 72 | it('get a RedisSMQ instance', function(done) { 73 | rsmq = new RedisSMQ({ 74 | realtime: true 75 | }); 76 | rsmq.should.be.an.instanceOf(RedisSMQ); 77 | done(); 78 | }); 79 | it('use an existing Redis Client', function(done) { 80 | rsmq2 = new RedisSMQ({ 81 | client: redis 82 | }); 83 | rsmq2.should.be.an.instanceOf(RedisSMQ); 84 | done(); 85 | }); 86 | it('should delete all leftover queues', function(done) { 87 | rsmq.deleteQueue({ 88 | qname: queue1.name 89 | }, function(err) {}); 90 | rsmq.deleteQueue({ 91 | qname: queue2.name 92 | }, function(err) {}); 93 | rsmq.deleteQueue({ 94 | qname: queue3.name 95 | }, function(err) {}); 96 | setTimeout(done, 100); 97 | }); 98 | describe('Promise Api', function() { 99 | it('should create a queue', function() { 100 | return rsmq.createQueueAsync({ 101 | qname: queue3.name, 102 | vt: 0, 103 | maxsize: -1 104 | }); 105 | }); 106 | it('should send a message', function() { 107 | return rsmq.sendMessageAsync({ 108 | qname: queue3.name, 109 | message: queue3.m1 110 | }); 111 | }); 112 | it('should send another message', function() { 113 | return rsmq.sendMessageAsync({ 114 | qname: queue3.name, 115 | message: queue3.m2 116 | }); 117 | }); 118 | it('should check the maxsize of the queue', function() { 119 | return rsmq.getQueueAttributesAsync({ 120 | qname: queue3.name 121 | }).then(function(resp) { 122 | resp.maxsize.should.equal(-1); 123 | }); 124 | }); 125 | it('should receive a message', function() { 126 | return rsmq.receiveMessageAsync({ 127 | qname: queue3.name, 128 | vt: 2 129 | }).then(function(resp) { 130 | resp.message.should.equal(queue3.m1); 131 | }); 132 | }); 133 | it('should receive another message', function() { 134 | return rsmq.receiveMessageAsync({ 135 | qname: queue3.name, 136 | vt: 1 137 | }).then(function(resp) { 138 | resp.message.should.equal(queue3.m2); 139 | }); 140 | }); 141 | it('Should fail: receive another message - no availabe message', function() { 142 | return rsmq.receiveMessageAsync({ 143 | qname: queue3.name, 144 | vt: 1 145 | }).then(function(resp) { 146 | should.not.exist(resp.id); 147 | }); 148 | }); 149 | it('wait 1010ms', function(done) { 150 | return setTimeout(done, 1010); 151 | }); 152 | it('should receive another message', function() { 153 | return rsmq.receiveMessageAsync({ 154 | qname: queue3.name, 155 | vt: 3 156 | }).then(function(resp) { 157 | resp.message.should.equal(queue3.m2); 158 | }); 159 | }); 160 | it('wait 1010ms', function(done) { 161 | return setTimeout(done, 1010); 162 | }); 163 | it('should receive another message', function() { 164 | return rsmq.receiveMessageAsync({ 165 | qname: queue3.name, 166 | vt: 3 167 | }).then(function(resp) { 168 | resp.message.should.equal(queue3.m1); 169 | }); 170 | }); 171 | it('should delete the created queue', function() { 172 | return rsmq.deleteQueueAsync({ 173 | qname: queue3.name 174 | }); 175 | }); 176 | }); 177 | describe('Queues', function() { 178 | it('Should fail: Create a new queue with invalid characters in name', function(done) { 179 | rsmq.createQueue({ 180 | qname: "should throw" 181 | }, function(err, resp) { 182 | err.message.should.equal("Invalid qname format"); 183 | done(); 184 | }); 185 | }); 186 | it('Should fail: Create a new queue with name longer 160 chars', function(done) { 187 | rsmq.createQueue({ 188 | qname: "name01234567890123456789012345678901234567890123456789012345678901234567890123456789name01234567890123456789012345678901234567890123456789012345678901234567890123456789" 189 | }, function(err, resp) { 190 | err.message.should.equal("Invalid qname format"); 191 | done(); 192 | }); 193 | }); 194 | it('Should fail: Create a new queue with negative vt', function(done) { 195 | rsmq.createQueue({ 196 | qname: queue1.name, 197 | vt: -20 198 | }, function(err, resp) { 199 | err.message.should.equal("vt must be between 0 and 9999999"); 200 | done(); 201 | }); 202 | }); 203 | it('Should fail: Create a new queue with negative vt - using createQueueAsync', function() { 204 | return rsmq.createQueueAsync({ 205 | qname: queue1.name, 206 | vt: -20 207 | }).should.be.rejectedWith(Error, { 208 | message: "vt must be between 0 and 9999999" 209 | }); 210 | }); 211 | it('Should fail: Create a new queue with non numeric vt', function(done) { 212 | rsmq.createQueue({ 213 | qname: queue1.name, 214 | vt: "not_a_number" 215 | }, function(err, resp) { 216 | err.message.should.equal("vt must be between 0 and 9999999"); 217 | done(); 218 | }); 219 | }); 220 | it('Should fail: Create a new queue with non numeric vt', function() { 221 | return rsmq.createQueueAsync({ 222 | qname: queue1.name, 223 | vt: "not_a_number" 224 | }).should.be.rejectedWith(Error, { 225 | message: "vt must be between 0 and 9999999" 226 | }); 227 | }); 228 | it('Should fail: Create a new queue with vt too high', function(done) { 229 | rsmq.createQueue({ 230 | qname: queue1.name, 231 | vt: 10000000 232 | }, function(err, resp) { 233 | err.message.should.equal("vt must be between 0 and 9999999"); 234 | done(); 235 | }); 236 | }); 237 | it('Should fail: Create a new queue with negative delay', function(done) { 238 | rsmq.createQueue({ 239 | qname: queue1.name, 240 | delay: -20 241 | }, function(err, resp) { 242 | err.message.should.equal("delay must be between 0 and 9999999"); 243 | done(); 244 | }); 245 | }); 246 | it('Should fail: Create a new queue with non numeric delay', function(done) { 247 | rsmq.createQueue({ 248 | qname: queue1.name, 249 | delay: "not_a_number" 250 | }, function(err, resp) { 251 | err.message.should.equal("delay must be between 0 and 9999999"); 252 | done(); 253 | }); 254 | }); 255 | it('Should fail: Create a new queue with delay too high', function(done) { 256 | rsmq.createQueue({ 257 | qname: queue1.name, 258 | delay: 10000000 259 | }, function(err, resp) { 260 | err.message.should.equal("delay must be between 0 and 9999999"); 261 | done(); 262 | }); 263 | }); 264 | it('Should fail: Create a new queue with negative maxsize', function(done) { 265 | rsmq.createQueue({ 266 | qname: queue1.name, 267 | maxsize: -20 268 | }, function(err, resp) { 269 | err.message.should.equal("maxsize must be between 1024 and 65536"); 270 | done(); 271 | }); 272 | }); 273 | it('Should fail: Create a new queue with non numeric maxsize', function(done) { 274 | rsmq.createQueue({ 275 | qname: queue1.name, 276 | maxsize: "not_a_number" 277 | }, function(err, resp) { 278 | err.message.should.equal("maxsize must be between 1024 and 65536"); 279 | done(); 280 | }); 281 | }); 282 | it('Should fail: Create a new queue with maxsize too high', function(done) { 283 | rsmq.createQueue({ 284 | qname: queue1.name, 285 | maxsize: 66000 286 | }, function(err, resp) { 287 | err.message.should.equal("maxsize must be between 1024 and 65536"); 288 | done(); 289 | }); 290 | }); 291 | it('Should fail: Create a new queue with maxsize too low', function(done) { 292 | rsmq.createQueue({ 293 | qname: queue1.name, 294 | maxsize: 900 295 | }, function(err, resp) { 296 | err.message.should.equal("maxsize must be between 1024 and 65536"); 297 | done(); 298 | }); 299 | }); 300 | it('Should fail: Create a new queue with maxsize `-2`', function(done) { 301 | rsmq.createQueue({ 302 | qname: queue1.name, 303 | maxsize: -2 304 | }, function(err, resp) { 305 | err.message.should.equal("maxsize must be between 1024 and 65536"); 306 | done(); 307 | }); 308 | }); 309 | it('ListQueues: Should return empty array', function(done) { 310 | rsmq.listQueues(function(err, resp) { 311 | should.not.exist(err); 312 | resp.length.should.equal(0); 313 | done(); 314 | }); 315 | }); 316 | it('Create a new queue: queue1', function(done) { 317 | rsmq.createQueue({ 318 | qname: queue1.name 319 | }, function(err, resp) { 320 | should.not.exist(err); 321 | resp.should.equal(1); 322 | done(); 323 | }); 324 | }); 325 | it('Should fail: Create the same queue again', function(done) { 326 | rsmq.createQueue({ 327 | qname: queue1.name 328 | }, function(err, resp) { 329 | err.message.should.equal("Queue exists"); 330 | done(); 331 | }); 332 | }); 333 | it('ListQueues: Should return array with one element', function(done) { 334 | rsmq.listQueues(function(err, resp) { 335 | should.not.exist(err); 336 | resp.length.should.equal(1); 337 | resp.should.containEql(queue1.name); 338 | done(); 339 | }); 340 | }); 341 | it('Create a new queue: queue2', function(done) { 342 | rsmq.createQueue({ 343 | qname: queue2.name, 344 | maxsize: 2048 345 | }, function(err, resp) { 346 | should.not.exist(err); 347 | resp.should.equal(1); 348 | done(); 349 | }); 350 | }); 351 | it('ListQueues: Should return array with two elements', function(done) { 352 | rsmq.listQueues(function(err, resp) { 353 | should.not.exist(err); 354 | resp.length.should.equal(2); 355 | resp.should.containEql(queue1.name); 356 | resp.should.containEql(queue2.name); 357 | done(); 358 | }); 359 | }); 360 | it('Should succeed: GetQueueAttributes of queue 1', function(done) { 361 | rsmq.getQueueAttributes({ 362 | qname: queue1.name 363 | }, function(err, resp) { 364 | should.not.exist(err); 365 | resp.msgs.should.equal(0); 366 | queue1.modified = resp.modified; 367 | done(); 368 | }); 369 | }); 370 | it('Should fail: GetQueueAttributes of bogus queue', function(done) { 371 | rsmq.getQueueAttributes({ 372 | qname: "sdfsdfsdf" 373 | }, function(err, resp) { 374 | err.message.should.equal("Queue not found"); 375 | done(); 376 | }); 377 | }); 378 | it('Should fail: setQueueAttributes of bogus queue with no supplied attributes', function(done) { 379 | rsmq.setQueueAttributes({ 380 | qname: "kjdsfh3h" 381 | }, function(err, resp) { 382 | err.message.should.equal("No attribute was supplied"); 383 | done(); 384 | }); 385 | }); 386 | it('Should fail: setQueueAttributes of bogus queue with supplied attributes', function(done) { 387 | rsmq.setQueueAttributes({ 388 | qname: "kjdsfh3h", 389 | vt: 1000 390 | }, function(err, resp) { 391 | err.message.should.equal("Queue not found"); 392 | done(); 393 | }); 394 | }); 395 | it('setQueueAttributes: Should return the queue with a new vt attribute', function(done) { 396 | rsmq.setQueueAttributes({ 397 | qname: queue1.name, 398 | vt: 1234 399 | }, function(err, resp) { 400 | resp.vt.should.equal(1234); 401 | resp.delay.should.equal(0); 402 | resp.maxsize.should.equal(65536); 403 | done(); 404 | }); 405 | }); 406 | it('setQueueAttributes: Should return the queue with a new delay attribute', function(done) { 407 | this.timeout(2000); 408 | setTimeout(function() { 409 | rsmq.setQueueAttributes({ 410 | qname: queue1.name, 411 | delay: 7 412 | }, function(err, resp) { 413 | resp.vt.should.equal(1234); 414 | resp.delay.should.equal(7); 415 | resp.maxsize.should.equal(65536); 416 | resp.modified.should.be.above(queue1.modified); 417 | done(); 418 | }); 419 | }, 1100); 420 | }); 421 | it('setQueueAttributes: Should return the queue with an umlimited maxsize', function(done) { 422 | rsmq.setQueueAttributes({ 423 | qname: queue1.name, 424 | maxsize: -1 425 | }, function(err, resp) { 426 | resp.vt.should.equal(1234); 427 | resp.delay.should.equal(7); 428 | resp.maxsize.should.equal(-1); 429 | done(); 430 | }); 431 | }); 432 | it('setQueueAttributes: Should return the queue with a new maxsize attribute', function(done) { 433 | rsmq.setQueueAttributes({ 434 | qname: queue1.name, 435 | maxsize: 2048 436 | }, function(err, resp) { 437 | resp.vt.should.equal(1234); 438 | resp.delay.should.equal(7); 439 | resp.maxsize.should.equal(2048); 440 | done(); 441 | }); 442 | }); 443 | it('setQueueAttributes: Should return the queue with a new attribute', function(done) { 444 | rsmq.setQueueAttributes({ 445 | qname: queue1.name, 446 | maxsize: 65536, 447 | vt: 30, 448 | delay: 0 449 | }, function(err, resp) { 450 | resp.vt.should.equal(30); 451 | resp.delay.should.equal(0); 452 | resp.maxsize.should.equal(65536); 453 | done(); 454 | }); 455 | }); 456 | it('Should fail:setQueueAttributes: Should not accept too small maxsize', function(done) { 457 | rsmq.setQueueAttributes({ 458 | qname: queue1.name, 459 | maxsize: 50 460 | }, function(err, resp) { 461 | err.message.should.equal("maxsize must be between 1024 and 65536"); 462 | done(); 463 | }); 464 | }); 465 | it('Should fail:setQueueAttributes: Should not accept negative value', function(done) { 466 | rsmq.setQueueAttributes({ 467 | qname: queue1.name, 468 | vt: -5 469 | }, function(err, resp) { 470 | err.message.should.equal("vt must be between 0 and 9999999"); 471 | done(); 472 | }); 473 | }); 474 | }); 475 | describe('Messages', function() { 476 | it('Should fail: Send a message to non-existing queue', function(done) { 477 | rsmq.sendMessage({ 478 | qname: "rtlbrmpft", 479 | message: "foo" 480 | }, function(err, resp) { 481 | err.message.should.equal("Queue not found"); 482 | done(); 483 | }); 484 | }); 485 | it('Should fail: Send a message without any parameters', function(done) { 486 | rsmq.sendMessage({}, function(err, resp) { 487 | err.message.should.equal("No qname supplied"); 488 | done(); 489 | }); 490 | }); 491 | it('Should fail: Send a message without a message key', function(done) { 492 | rsmq.sendMessage({ 493 | qname: queue1.name, 494 | messXage: "Hello" 495 | }, function(err, resp) { 496 | err.message.should.equal("Message must be a string"); 497 | done(); 498 | }); 499 | }); 500 | it('Should fail: Send a message with message being a number', function(done) { 501 | rsmq.sendMessage({ 502 | qname: queue1.name, 503 | message: 123 504 | }, function(err, resp) { 505 | err.message.should.equal("Message must be a string"); 506 | done(); 507 | }); 508 | }); 509 | // TODO: Try to send a loooong msg 510 | it('Send message 1 with existing Redis instance', function(done) { 511 | rsmq2.sendMessage({ 512 | qname: queue1.name, 513 | message: "Hello" 514 | }, function(err, resp) { 515 | should.not.exist(err); 516 | q1m1 = { 517 | id: resp, 518 | message: "Hello" 519 | }; 520 | done(); 521 | }); 522 | }); 523 | // Send 1000 msgs to q2 so we can delay sending of msg 2 to q1 524 | it('Send 1000 messages to queue2: succeed', function(done) { 525 | var i, j, pq; 526 | pq = []; 527 | for (i = j = 0; j < 1000; i = ++j) { 528 | pq.push({ 529 | qname: queue2.name, 530 | message: "test message number:" + i 531 | }); 532 | } 533 | async.map(pq, rsmq.sendMessage, function(err, resp) { 534 | var e, k, len; 535 | for (k = 0, len = resp.length; k < len; k++) { 536 | e = resp[k]; 537 | q2msgs[e] = 1; 538 | e.length.should.equal(32); 539 | } 540 | _.keys(q2msgs).length.should.equal(1000); 541 | done(); 542 | }); 543 | }); 544 | it('Send message 2', function(done) { 545 | rsmq.sendMessage({ 546 | qname: queue1.name, 547 | message: "World" 548 | }, function(err, resp) { 549 | should.not.exist(err); 550 | q1m2 = { 551 | id: resp, 552 | message: "World" 553 | }; 554 | done(); 555 | }); 556 | }); 557 | it('Receive a message. Should return message 1', function(done) { 558 | rsmq2.receiveMessage({ 559 | qname: queue1.name 560 | }, function(err, resp) { 561 | resp.id.should.equal(q1m1.id); 562 | done(); 563 | }); 564 | }); 565 | it('Receive a message. Should return message 2', function(done) { 566 | rsmq.receiveMessage({ 567 | qname: queue1.name 568 | }, function(err, resp) { 569 | resp.id.should.equal(q1m2.id); 570 | done(); 571 | }); 572 | }); 573 | it('Check queue properties. Should have 2 msgs', function(done) { 574 | rsmq.getQueueAttributes({ 575 | qname: queue1.name 576 | }, function(err, resp) { 577 | resp.msgs.should.equal(2); 578 | resp.hiddenmsgs.should.equal(2); 579 | done(); 580 | }); 581 | }); 582 | it('Send message 3', function(done) { 583 | rsmq.sendMessage({ 584 | qname: queue1.name, 585 | message: "Booo!!" 586 | }, function(err, resp) { 587 | should.not.exist(err); 588 | q1m3 = { 589 | id: resp, 590 | message: "Booo!!" 591 | }; 592 | done(); 593 | }); 594 | }); 595 | it('Check queue properties. Should have 3 msgs', function(done) { 596 | rsmq.getQueueAttributes({ 597 | qname: queue1.name 598 | }, function(err, resp) { 599 | resp.msgs.should.equal(3); 600 | resp.totalrecv.should.equal(2); 601 | done(); 602 | }); 603 | }); 604 | it('Pop a message. Should return message 3 and delete it', function(done) { 605 | rsmq.popMessage({ 606 | qname: queue1.name 607 | }, function(err, resp) { 608 | resp.id.should.equal(q1m3.id); 609 | done(); 610 | }); 611 | }); 612 | it('Check queue properties. Should have 2 msgs', function(done) { 613 | rsmq.getQueueAttributes({ 614 | qname: queue1.name 615 | }, function(err, resp) { 616 | resp.msgs.should.equal(2); 617 | resp.totalrecv.should.equal(3); 618 | done(); 619 | }); 620 | }); 621 | it('Pop a message. Should not return a message', function(done) { 622 | rsmq.popMessage({ 623 | qname: queue1.name 624 | }, function(err, resp) { 625 | should.not.exist(resp.id); 626 | done(); 627 | }); 628 | }); 629 | it('Should fail. Set the visibility of a non existing message', function(done) { 630 | rsmq.changeMessageVisibility({ 631 | qname: queue1.name, 632 | id: "abcdefghij0123456789abcdefghij01", 633 | vt: 10 634 | }, function(err, resp) { 635 | resp.should.equal(0); 636 | done(); 637 | }); 638 | }); 639 | it('Set new visibility timeout of message 2 to 10s', function(done) { 640 | rsmq.changeMessageVisibility({ 641 | qname: queue1.name, 642 | id: q1m2.id, 643 | vt: 10 644 | }, function(err, resp) { 645 | resp.should.equal(1); 646 | done(); 647 | }); 648 | }); 649 | it('Receive a message. Should return nothing', function(done) { 650 | rsmq.receiveMessage({ 651 | qname: queue1.name 652 | }, function(err, resp) { 653 | should.not.exist(resp.id); 654 | done(); 655 | }); 656 | }); 657 | it('Set new visibility timeout of message 2 to 0s', function(done) { 658 | rsmq.changeMessageVisibility({ 659 | qname: queue1.name, 660 | id: q1m2.id, 661 | vt: 0 662 | }, function(err, resp) { 663 | resp.should.equal(1); 664 | done(); 665 | }); 666 | }); 667 | it('Receive a message. Should return message 2', function(done) { 668 | rsmq.receiveMessage({ 669 | qname: queue1.name 670 | }, function(err, resp) { 671 | resp.id.should.equal(q1m2.id); 672 | done(); 673 | }); 674 | }); 675 | it('Receive a message. Should return nothing', function(done) { 676 | rsmq.receiveMessage({ 677 | qname: queue1.name 678 | }, function(err, resp) { 679 | should.not.exist(resp.id); 680 | done(); 681 | }); 682 | }); 683 | it('Should fail: Delete a message without supplying an id', function(done) { 684 | rsmq.deleteMessage({ 685 | qname: queue1.name 686 | }, function(err, resp) { 687 | err.message.should.equal("No id supplied"); 688 | done(); 689 | }); 690 | }); 691 | it('Should fail: Delete a message with invalid id', function(done) { 692 | rsmq.deleteMessage({ 693 | qname: queue1.name, 694 | id: "sdafsdf" 695 | }, function(err, resp) { 696 | err.message.should.equal("Invalid id format"); 697 | done(); 698 | }); 699 | }); 700 | it('Delete message 1. Should return 1', function(done) { 701 | rsmq.deleteMessage({ 702 | qname: queue1.name, 703 | id: q1m1.id 704 | }, function(err, resp) { 705 | resp.should.equal(1); 706 | done(); 707 | }); 708 | }); 709 | it('Delete message 1 again. Should return 0', function(done) { 710 | rsmq.deleteMessage({ 711 | qname: queue1.name, 712 | id: q1m1.id 713 | }, function(err, resp) { 714 | resp.should.equal(0); 715 | done(); 716 | }); 717 | }); 718 | it('Set new visibility timeout of message 1. Should return 0.', function(done) { 719 | rsmq.changeMessageVisibility({ 720 | qname: queue1.name, 721 | id: q1m1.id, 722 | vt: 10 723 | }, function(err, resp) { 724 | resp.should.equal(0); 725 | done(); 726 | }); 727 | }); 728 | it('Should fail: Send a message that is too long', function(done) { 729 | var text; 730 | text = JSON.stringify((function() { 731 | var results = []; 732 | for (var j = 0; j <= 15000; j++){ results.push(j); } 733 | return results; 734 | }).apply(this)); 735 | rsmq.sendMessage({ 736 | qname: queue1.name, 737 | message: text 738 | }, function(err, resp) { 739 | should.not.exist(resp); 740 | err.message.should.equal("Message too long"); 741 | done(); 742 | }); 743 | }); 744 | it('Receive 1000 messages from queue2 and delete 500 (those where number is even)', function(done) { 745 | var i, j, pq; 746 | pq = []; 747 | // we keep vt = 0 so we can query them again quickly 748 | for (i = j = 0; j < 1000; i = ++j) { 749 | pq.push({ 750 | qname: queue2.name, 751 | vt: 0 752 | }); 753 | } 754 | async.map(pq, rsmq.receiveMessage, function(err, resp) { 755 | var dq, e, k, len; 756 | dq = []; 757 | for (k = 0, len = resp.length; k < len; k++) { 758 | e = resp[k]; 759 | if (!(!(e.message.split(":")[1] % 2))) { 760 | continue; 761 | } 762 | dq.push({ 763 | qname: queue2.name, 764 | id: e.id 765 | }); 766 | delete q2msgs[e.id]; 767 | } 768 | async.map(dq, rsmq.deleteMessage, function(err, resp) { 769 | var l, len1; 770 | for (l = 0, len1 = resp.length; l < len1; l++) { 771 | e = resp[l]; 772 | e.should.equal(1); 773 | } 774 | done(); 775 | }); 776 | }); 777 | }); 778 | it('GetQueueAttributes: Should return queue attributes', function(done) { 779 | rsmq.getQueueAttributes({ 780 | qname: queue2.name 781 | }, function(err, resp) { 782 | should.not.exist(err); 783 | resp.msgs.should.equal(500); 784 | done(); 785 | }); 786 | }); 787 | it('Receive 500 messages from queue2 and delete them', function(done) { 788 | var i, j, pq; 789 | pq = []; 790 | // we keep vt = 0 so we can query them again quickly 791 | for (i = j = 0; j < 500; i = ++j) { 792 | pq.push({ 793 | qname: queue2.name, 794 | vt: 0 795 | }); 796 | } 797 | async.map(pq, rsmq.receiveMessage, function(err, resp) { 798 | var dq, e, k, len; 799 | dq = []; 800 | for (k = 0, len = resp.length; k < len; k++) { 801 | e = resp[k]; 802 | if (!(e.message.split(":")[1] % 2)) { 803 | continue; 804 | } 805 | dq.push({ 806 | qname: queue2.name, 807 | id: e.id 808 | }); 809 | delete q2msgs[e.id]; 810 | } 811 | async.map(dq, rsmq.deleteMessage, function(err, resp) { 812 | var l, len1; 813 | for (l = 0, len1 = resp.length; l < len1; l++) { 814 | e = resp[l]; 815 | e.should.equal(1); 816 | } 817 | done(); 818 | // q2msgs should be empty 819 | _.keys(q2msgs).length.should.equal(0); 820 | }); 821 | }); 822 | }); 823 | it('Receive a message from queue2. Should return {}', function(done) { 824 | rsmq.receiveMessage({ 825 | qname: queue2.name 826 | }, function(err, resp) { 827 | should.not.exist(resp.id); 828 | done(); 829 | }); 830 | }); 831 | it('GetQueueAttributes: Should return queue attributes', function(done) { 832 | rsmq.getQueueAttributes({ 833 | qname: queue2.name 834 | }, function(err, resp) { 835 | should.not.exist(err); 836 | resp.totalrecv.should.equal(1500); 837 | resp.totalsent.should.equal(1000); 838 | resp.msgs.should.equal(0); 839 | done(); 840 | }); 841 | }); 842 | it('setQueueAttributes: Should return the queue2 with an umlimited maxsize', function(done) { 843 | rsmq.setQueueAttributes({ 844 | qname: queue2.name, 845 | delay: 0, 846 | vt: 30, 847 | maxsize: -1 848 | }, function(err, resp) { 849 | resp.vt.should.equal(30); 850 | resp.delay.should.equal(0); 851 | resp.maxsize.should.equal(-1); 852 | done(); 853 | }); 854 | }); 855 | it('Send/Recevice a longer than 64k msg to test unlimited functionality', function(done) { 856 | var longmsg; 857 | longmsg = looong_string(); 858 | rsmq.sendMessage({ 859 | qname: queue2.name, 860 | message: longmsg 861 | }, function(err, resp1) { 862 | should.not.exist(err); 863 | rsmq.receiveMessage({ 864 | qname: queue2.name 865 | }, function(err, resp2) { 866 | should.not.exist(err); 867 | resp2.message.should.equal(longmsg); 868 | resp2.id.should.equal(resp1); 869 | done(); 870 | }); 871 | }); 872 | }); 873 | }); 874 | describe('Realtime Pub/Sub notifications', function() { 875 | it('Send another message to queue1', function(done) { 876 | rsmq.sendMessage({ 877 | qname: queue1.name, 878 | message: "Another World" 879 | }, function(err, resp) { 880 | should.not.exist(err); 881 | done(); 882 | }); 883 | }); 884 | it('wait 100ms', function(done) { 885 | return setTimeout(done, 100); 886 | }); 887 | it('check queue1 length. Should be 2', function(done) { 888 | Q1LENGTH.should.equal(2); 889 | done(); 890 | }); 891 | it('Send another message to queue1', function(done) { 892 | rsmq.sendMessage({ 893 | qname: queue1.name, 894 | message: "Another World" 895 | }, function(err, resp) { 896 | should.not.exist(err); 897 | done(); 898 | }); 899 | }); 900 | it('wait 100ms', function(done) { 901 | return setTimeout(done, 100); 902 | }); 903 | it('check queue1 length. Should be 3', function(done) { 904 | Q1LENGTH.should.equal(3); 905 | done(); 906 | }); 907 | }); 908 | }); 909 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "noImplicitAny": false, 5 | "target": "es6", 6 | "removeComments": true, 7 | "preserveConstEnums": true, 8 | "sourceMap": false, 9 | "watch": false, 10 | "outDir": "./" 11 | }, 12 | "include": [ 13 | "_src/*.ts" 14 | ], 15 | "exclude": [ 16 | "node_modules" 17 | ] 18 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "linterOptions": { 3 | "exclude": [ "./app/apidoc.ts" ] 4 | }, 5 | "rules": { 6 | "no-unused-expression": true, 7 | "class-name": true, 8 | "comment-format": [ 9 | true, 10 | "check-space" 11 | ], 12 | "indent": [ 13 | true, 14 | "tabs" 15 | ], 16 | "no-eval": true, 17 | "no-internal-module": true, 18 | "no-trailing-whitespace": true, 19 | "no-unused-variable": true, 20 | "no-unsafe-finally": true, 21 | "no-var-keyword": true, 22 | "one-line": [ 23 | true, 24 | "check-open-brace", 25 | "check-whitespace" 26 | ], 27 | "quotemark": [ 28 | true, 29 | "double" 30 | ], 31 | "semicolon": [ 32 | true, 33 | "always" 34 | ], 35 | "triple-equals": [ 36 | true, 37 | "allow-null-check" 38 | ], 39 | "typedef-whitespace": [ 40 | true, 41 | { 42 | "call-signature": "nospace", 43 | "index-signature": "nospace", 44 | "parameter": "nospace", 45 | "property-declaration": "nospace", 46 | "variable-declaration": "nospace" 47 | } 48 | ], 49 | "variable-name": [ 50 | true, 51 | "ban-keywords" 52 | ], 53 | "whitespace": [ 54 | true, 55 | "check-branch", 56 | "check-decl", 57 | "check-operator", 58 | "check-separator", 59 | "check-type" 60 | ], 61 | "member-access": [ 62 | true, 63 | "check-accessor" 64 | ], 65 | "jsdoc-format": true, 66 | "import-spacing": true, 67 | "interface-name": [true, "always-prefix"], 68 | "new-parens": true, 69 | "no-boolean-literal-compare": false, 70 | "radix": true 71 | } 72 | } --------------------------------------------------------------------------------