├── .gitignore ├── .travis.yml ├── README.md ├── mongodb-queue.js ├── package-lock.json ├── package.json └── test ├── clean.js ├── dead-queue.js ├── default.js ├── delay.js ├── indexes.js ├── many.js ├── multi.js ├── ping.js ├── setup.js ├── stats.js └── visibility.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | *.log 3 | *~ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | - "10" 5 | - "11" 6 | services: mongodb 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mongodb-queue # 2 | 3 | [![Build Status](https://travis-ci.org/chilts/mongodb-queue.png)](https://travis-ci.org/chilts/mongodb-queue) [![NPM](https://nodei.co/npm/mongodb-queue.png?mini=true)](https://nodei.co/npm/mongodb-queue/) 4 | 5 | A really light-weight way to create queues with a nice API if you're already 6 | using MongoDB. 7 | 8 | Now compatible with the MongoDB v3 driver. 9 | 10 | For MongoDB v2 driver use mongodb-queue@3. 11 | 12 | **NOTE**: This package is considered feature complete and **STABLE** hence there is not a whole lot of development on 13 | it though it is being used extensively. Use it with all your might and let us know of any problems - it should be 14 | bullet-proof. 15 | 16 | ## Synopsis ## 17 | 18 | Create a connection to your MongoDB database, and use it to create a queue object: 19 | 20 | ```js 21 | var mongodb = require('mongodb') 22 | var mongoDbQueue = require('mongodb-queue') 23 | 24 | const url = 'mongodb://localhost:27017/' 25 | const client = new mongodb.MongoClient(url, { useNewUrlParser: true }) 26 | 27 | client.connect(err => { 28 | const db = client.db('test') 29 | const queue = mongoDbQueue(db, 'my-queue') 30 | 31 | // ... 32 | 33 | }) 34 | ``` 35 | 36 | Add a message to a queue: 37 | 38 | ```js 39 | queue.add('Hello, World!', (err, id) => { 40 | // Message with payload 'Hello, World!' added. 41 | // 'id' is returned, useful for logging. 42 | }) 43 | ``` 44 | 45 | Get a message from the queue: 46 | 47 | ```js 48 | queue.get((err, msg) => { 49 | console.log('msg.id=' + msg.id) 50 | console.log('msg.ack=' + msg.ack) 51 | console.log('msg.payload=' + msg.payload) // 'Hello, World!' 52 | console.log('msg.tries=' + msg.tries) 53 | }) 54 | ``` 55 | 56 | Ping a message to keep it's visibility open for long-running tasks 57 | 58 | ```js 59 | queue.ping(msg.ack, (err, id) => { 60 | // Visibility window now increased for this message id. 61 | // 'id' is returned, useful for logging. 62 | }) 63 | ``` 64 | 65 | Ack a message (and remove it from the queue): 66 | 67 | ```js 68 | queue.ack(msg.ack, (err, id) => { 69 | // This msg removed from queue for this ack. 70 | // The 'id' of the message is returned, useful for logging. 71 | }) 72 | ``` 73 | 74 | By default, all old messages - even processed ones - are left in MongoDB. This is so that 75 | you can go and analyse them if you want. However, you can call the following function 76 | to remove processed messages: 77 | 78 | ```js 79 | queue.clean((err) => { 80 | // All processed (ie. acked) messages have been deleted 81 | }) 82 | ``` 83 | 84 | And if you haven't already, you should call this to make sure indexes have 85 | been added in MongoDB. Of course, if you've called this once (in some kind 86 | one-off script) you don't need to call it in your program. Of course, check 87 | the changelock to see if you need to update them with new releases: 88 | 89 | ```js 90 | queue.createIndexes((err, indexName) => { 91 | // The indexes needed have been added to MongoDB. 92 | }) 93 | ``` 94 | 95 | ## Creating a Queue ## 96 | 97 | To create a queue, call the exported function with the `MongoClient`, the name 98 | and a set of opts. The MongoDB collection used is the same name as the name 99 | passed in: 100 | 101 | ``` 102 | var mongoDbQueue = require('mongodb-queue') 103 | 104 | // an instance of a queue 105 | var queue1 = mongoDbQueue(db, 'a-queue') 106 | // another queue which uses the same collection as above 107 | var queue2 = mongoDbQueue(db, 'a-queue') 108 | ``` 109 | 110 | Using `queue1` and `queue2` here won't interfere with each other and will play along nicely, but that's not 111 | a good idea code-wise - just use the same object. This example is for illustrative purposes only. 112 | 113 | Note: Don't use the same queue name twice with different options, otherwise behaviour is undefined and again 114 | it's not something you should do. 115 | 116 | To pass in options for the queue: 117 | 118 | ``` 119 | var resizeQueue = mongoDbQueue(db, 'resize-queue', { visibility : 30, delay : 15 }) 120 | ``` 121 | 122 | This example shows a queue with a message visibility of 30s and a delay to each message of 15s. 123 | 124 | ## Options ## 125 | 126 | ### name ### 127 | 128 | This is the name of the MongoDB Collection you wish to use to store the messages. 129 | Each queue you create will be it's own collection. 130 | 131 | e.g. 132 | 133 | ``` 134 | var resizeImageQueue = mongoDbQueue(db, 'resize-image-queue') 135 | var notifyOwnerQueue = mongoDbQueue(db, 'notify-owner-queue') 136 | ``` 137 | 138 | This will create two collections in MongoDB called `resize-image-queue` and `notify-owner-queue`. 139 | 140 | ### visibility - Message Visibility Window ### 141 | 142 | Default: `30` 143 | 144 | By default, if you don't ack a message within the first 30s after receiving it, 145 | it is placed back in the queue so it can be fetched again. This is called the 146 | visibility window. 147 | 148 | You may set this visibility window on a per queue basis. For example, to set the 149 | visibility to 15 seconds: 150 | 151 | ``` 152 | var queue = mongoDbQueue(db, 'queue', { visibility : 15 }) 153 | ``` 154 | 155 | All messages in this queue now have a visibility window of 15s, instead of the 156 | default 30s. 157 | 158 | ### delay - Delay Messages on Queue ### 159 | 160 | Default: `0` 161 | 162 | When a message is added to a queue, it is immediately available for retrieval. 163 | However, there are times when you might like to delay messages coming off a queue. 164 | ie. if you set delay to be `10`, then every message will only be available for 165 | retrieval 10s after being added. 166 | 167 | To delay all messages by 10 seconds, try this: 168 | 169 | ``` 170 | var queue = mongoDbQueue(db, 'queue', { delay : 10 }) 171 | ``` 172 | 173 | This is now the default for every message added to the queue. 174 | 175 | ### deadQueue - Dead Message Queue ### 176 | 177 | Default: none 178 | 179 | Messages that have been retried over `maxRetries` will be pushed to this queue so you can 180 | automatically see problem messages. 181 | 182 | Pass in a queue (that you created) onto which these messages will be pushed: 183 | 184 | ```js 185 | var deadQueue = mongoDbQueue(db, 'dead-queue') 186 | var queue = mongoDbQueue(db, 'queue', { deadQueue : deadQueue }) 187 | ``` 188 | 189 | If you pop a message off the `queue` over `maxRetries` times and still have not acked it, 190 | it will be pushed onto the `deadQueue` for you. This happens when you `.get()` (not when 191 | you miss acking a message in it's visibility window). By doing it when you call `.get()`, 192 | the unprocessed message will be received, pushed to the `deadQueue`, acked off the normal 193 | queue and `.get()` will check for new messages prior to returning you one (or none). 194 | 195 | ### maxRetries - Maximum Retries per Message ### 196 | 197 | Default: 5 198 | 199 | This option only comes into effect if you pass in a `deadQueue` as shown above. What this 200 | means is that if an item is popped off the queue `maxRetries` times (e.g. 5) and not acked, 201 | it will be moved to this `deadQueue` the next time it is tried to pop off. You can poll your 202 | `deadQueue` for dead messages much like you can poll your regular queues. 203 | 204 | The payload of the messages in the dead queue are the entire messages returned when `.get()`ing 205 | them from the original queue. 206 | 207 | e.g. 208 | 209 | Given this message: 210 | 211 | ``` 212 | msg = { 213 | id: '533b1eb64ee78a57664cc76c', 214 | ack: 'c8a3cc585cbaaacf549d746d7db72f69', 215 | payload: 'Hello, World!', 216 | tries: 1 217 | } 218 | ``` 219 | 220 | If it is not acked within the `maxRetries` times, then when you receive this same message 221 | from the `deadQueue`, it may look like this: 222 | 223 | ``` 224 | msg = { 225 | id: '533b1ecf3ca3a76b667671ef', 226 | ack: '73872b204e3f7be84050a1ce82c5c9c0', 227 | payload: { 228 | id: '533b1eb64ee78a57664cc76c', 229 | ack: 'c8a3cc585cbaaacf549d746d7db72f69', 230 | payload: 'Hello, World!', 231 | tries: 5 232 | }, 233 | tries: 1 234 | } 235 | ``` 236 | 237 | Notice that the payload from the `deadQueue` is exactly the same as the original message 238 | when it was on the original queue (except with the number of tries set to 5). 239 | 240 | ## Operations ## 241 | 242 | ### .add() ### 243 | 244 | You can add a string to the queue: 245 | 246 | ```js 247 | queue.add('Hello, World!', (err, id) => { 248 | // Message with payload 'Hello, World!' added. 249 | // 'id' is returned, useful for logging. 250 | }) 251 | ``` 252 | 253 | Or add an object of your choosing: 254 | 255 | ```js 256 | queue.add({ err: 'E_BORKED', msg: 'Broken' }, (err, id) => { 257 | // Message with payload { err: 'E_BORKED', msg: 'Broken' } added. 258 | // 'id' is returned, useful for logging. 259 | }) 260 | ``` 261 | 262 | Or add multiple messages: 263 | 264 | ```js 265 | queue.add(['msg1', 'msg2', 'msg3'], (err, ids) => { 266 | // Messages with payloads 'msg1', 'msg2' & 'msg3' added. 267 | // All 'id's are returned as an array, useful for logging. 268 | }) 269 | ``` 270 | 271 | You can delay individual messages from being visible by passing the `delay` option: 272 | 273 | ```js 274 | queue.add('Later', { delay: 120 }, (err, id) => { 275 | // Message with payload 'Later' added. 276 | // 'id' is returned, useful for logging. 277 | // This message won't be available for getting for 2 mins. 278 | }) 279 | ``` 280 | 281 | ### .get() ### 282 | 283 | Retrieve a message from the queue: 284 | 285 | ```js 286 | queue.get((err, msg) => { 287 | // You can now process the message 288 | // IMPORTANT: The callback will not wait for an message if the queue is empty. The message will be undefined if the queue is empty. 289 | }) 290 | ``` 291 | 292 | You can choose the visibility of an individual retrieved message by passing the `visibility` option: 293 | 294 | ```js 295 | queue.get({ visibility: 10 }, (err, msg) => { 296 | // You can now process the message for 10s before it goes back into the queue if not ack'd instead of the duration that is set on the queue in general 297 | }) 298 | ``` 299 | 300 | Message will have the following structure: 301 | 302 | ```js 303 | { 304 | id: '533b1eb64ee78a57664cc76c', // ID of the message 305 | ack: 'c8a3cc585cbaaacf549d746d7db72f69', // ID for ack and ping operations 306 | payload: 'Hello, World!', // Payload passed when the message was addded 307 | tries: 1 // Number of times this message has been retrieved from queue without being ack'd 308 | } 309 | ``` 310 | 311 | ### .ack() ### 312 | 313 | After you have received an item from a queue and processed it, you can delete it 314 | by calling `.ack()` with the unique `ackId` returned: 315 | 316 | ```js 317 | queue.get((err, msg) => { 318 | queue.ack(msg.ack, (err, id) => { 319 | // this message has now been removed from the queue 320 | }) 321 | }) 322 | ``` 323 | 324 | ### .ping() ### 325 | 326 | After you have received an item from a queue and you are taking a while 327 | to process it, you can `.ping()` the message to tell the queue that you are 328 | still alive and continuing to process the message: 329 | 330 | ```js 331 | queue.get((err, msg) => { 332 | queue.ping(msg.ack, (err, id) => { 333 | // this message has had it's visibility window extended 334 | }) 335 | }) 336 | ``` 337 | 338 | You can also choose the visibility time that gets added by the ping operation by passing the `visibility` option: 339 | 340 | ```js 341 | queue.get((err, msg) => { 342 | queue.ping(msg.ack, { visibility: 10 }, (err, id) => { 343 | // this message has had it's visibility window extended by 10s instead of the visibilty set on the queue in general 344 | }) 345 | }) 346 | ``` 347 | 348 | ### .total() ### 349 | 350 | Returns the total number of messages that has ever been in the queue, including 351 | all current messages: 352 | 353 | ```js 354 | queue.total((err, count) => { 355 | console.log('This queue has seen %d messages', count) 356 | }) 357 | ``` 358 | 359 | ### .size() ### 360 | 361 | Returns the total number of messages that are waiting in the queue. 362 | 363 | ```js 364 | queue.size((err, count) => { 365 | console.log('This queue has %d current messages', count) 366 | }) 367 | ``` 368 | 369 | ### .inFlight() ### 370 | 371 | Returns the total number of messages that are currently in flight. ie. that 372 | have been received but not yet acked: 373 | 374 | ```js 375 | queue.inFlight((err, count) => { 376 | console.log('A total of %d messages are currently being processed', count) 377 | }) 378 | ``` 379 | 380 | ### .done() ### 381 | 382 | Returns the total number of messages that have been processed correctly in the 383 | queue: 384 | 385 | ```js 386 | queue.done((err, count) => { 387 | console.log('This queue has processed %d messages', count) 388 | }) 389 | ``` 390 | 391 | ### .clean() ### 392 | 393 | Deletes all processed mesages from the queue. Of course, you can leave these hanging around 394 | if you wish, but delete them if you no longer need them. Perhaps do this using `setInterval` 395 | for a regular cleaning: 396 | 397 | ```js 398 | queue.clean((err) => { 399 | console.log('The processed messages have been deleted from the queue') 400 | }) 401 | ``` 402 | 403 | ### Notes about Numbers ### 404 | 405 | If you add up `.size() + .inFlight() + .done()` then you should get `.total()` 406 | but this will only be approximate since these are different operations hitting the database 407 | at slightly different times. Hence, a message or two might be counted twice or not at all 408 | depending on message turnover at any one time. You should not rely on these numbers for 409 | anything but are included as approximations at any point in time. 410 | 411 | ## Use of MongoDB ## 412 | 413 | Whilst using MongoDB recently and having a need for lightweight queues, I realised 414 | that the atomic operations that MongoDB provides are ideal for this kind of job. 415 | 416 | Since everything it atomic, it is impossible to lose messages in or around your 417 | application. I guess MongoDB could lose them but it's a safer bet it won't compared 418 | to your own application. 419 | 420 | As an example of the atomic nature being used, messages stay in the same collection 421 | and are never moved around or deleted, just a couple of fields are set, incremented 422 | or deleted. We always use MongoDB's excellent `collection.findAndModify()` so that 423 | each message is updated atomically inside MongoDB and we never have to fetch something, 424 | change it and store it back. 425 | 426 | ## Roadmap ## 427 | 428 | We may add the ability for each function to return a promise in the future so it can be used as such, or with 429 | async/await. 430 | 431 | ## Releases ## 432 | 433 | ### 4.0.0 (2019-02-20) ### 434 | 435 | * [NEW] Updated entire codebase to be compatible with the mongodb driver v3 436 | 437 | ### 2.1.0 (2016-04-21) ### 438 | 439 | * [FIX] Fix to indexes (thanks https://github.com/ifightcrime) when lots of messages 440 | 441 | ### 2.0.0 (2014-11-12) ### 442 | 443 | * [NEW] Update MongoDB API from v1 to v2 (thanks https://github.com/hanwencheng) 444 | 445 | ### 1.0.1 (2015-05-25) ### 446 | 447 | * [NEW] Test changes only 448 | 449 | ### 1.0.0 (2014-10-30) ### 450 | 451 | * [NEW] Ability to specify a visibility window when getting a message (thanks https://github.com/Gertt) 452 | 453 | ### 0.9.1 (2014-08-28) ### 454 | 455 | * [NEW] Added .clean() method to remove old (processed) messages 456 | * [NEW] Add 'delay' option to queue.add() so individual messages can be delayed separately 457 | * [TEST] Test individual 'delay' option for each message 458 | 459 | ### 0.7.0 (2014-03-24) ### 460 | 461 | * [FIX] Fix .ping() so only visible/non-deleted messages can be pinged 462 | * [FIX] Fix .ack() so only visible/non-deleted messages can be pinged 463 | * [TEST] Add test to make sure messages can't be acked twice 464 | * [TEST] Add test to make sure an acked message can't be pinged 465 | * [INTERNAL] Slight function name changes, nicer date routines 466 | 467 | ### 0.6.0 (2014-03-22) ### 468 | 469 | * [NEW] The msg.id is now returned on successful Queue.ping() and Queue.ack() calls 470 | * [NEW] Call quueue.ensureIndexes(callback) to create them 471 | * [CHANGE] When a message is acked, 'deleted' is now set to the current time (not true) 472 | * [CHANGE] The queue is now created synchronously 473 | 474 | ### 0.5.0 (2014-03-21) ### 475 | 476 | * [NEW] Now adds two indexes onto the MongoDB collection used for the message 477 | * [CHANGE] The queue is now created by calling the async exported function 478 | * [DOC] Update to show how the queues are now created 479 | 480 | ### 0.4.0 (2014-03-20) ### 481 | 482 | * [NEW] Ability to ping retrieved messages a. la. 'still alive' and 'extend visibility' 483 | * [CHANGE] Removed ability to have different queues in the same collection 484 | * [CHANGE] All queues are now stored in their own collection 485 | * [CHANGE] When acking a message, only need ack (no longer need id) 486 | * [TEST] Added test for pinged messages 487 | * [DOC] Update to specify each queue will create it's own MongoDB collection 488 | * [DOC] Added docs for option `delay` 489 | * [DOC] Added synopsis for Queue.ping() 490 | * [DOC] Removed use of msg.id when calling Queue.ack() 491 | 492 | ### 0.3.1 (2014-03-19) ### 493 | 494 | * [DOC] Added documentation for the `delay` option 495 | 496 | ### 0.3.0 (2014-03-19) ### 497 | 498 | * [NEW] Return the message id when added to a queue 499 | * [NEW] Ability to set a default delay on all messages in a queue 500 | * [FIX] Make sure old messages (outside of visibility window) aren't deleted when acked 501 | * [FIX] Internal: Fix `queueName` 502 | * [TEST] Added test for multiple messages 503 | * [TEST] Added test for delayed messages 504 | 505 | ### 0.2.1 (2014-03-19) ### 506 | 507 | * [FIX] Fix when getting messages off an empty queue 508 | * [NEW] More Tests 509 | 510 | ### 0.2.0 (2014-03-18) ### 511 | 512 | * [NEW] messages now return number of tries (times they have been fetched) 513 | 514 | ### 0.1.0 (2014-03-18) ### 515 | 516 | * [NEW] add messages to queues 517 | * [NEW] fetch messages from queues 518 | * [NEW] ack messages on queues 519 | * [NEW] set up multiple queues 520 | * [NEW] set your own MongoDB Collection name 521 | * [NEW] set a visibility timeout on a queue 522 | 523 | ## Author ## 524 | 525 | ``` 526 | $ npx chilts 527 | 528 | ╒════════════════════════════════════════════════════╕ 529 | │ │ 530 | │ Andrew Chilton (Personal) │ 531 | │ ------------------------- │ 532 | │ │ 533 | │ Email : andychilton@gmail.com │ 534 | │ Web : https://chilts.org │ 535 | │ Twitter : https://twitter.com/andychilton │ 536 | │ GitHub : https://github.com/chilts │ 537 | │ GitLab : https://gitlab.org/chilts │ 538 | │ │ 539 | │ Apps Attic Ltd (My Company) │ 540 | │ --------------------------- │ 541 | │ │ 542 | │ Email : chilts@appsattic.com │ 543 | │ Web : https://appsattic.com │ 544 | │ Twitter : https://twitter.com/AppsAttic │ 545 | │ GitLab : https://gitlab.com/appsattic │ 546 | │ │ 547 | │ Node.js / npm │ 548 | │ ------------- │ 549 | │ │ 550 | │ Profile : https://www.npmjs.com/~chilts │ 551 | │ Card : $ npx chilts │ 552 | │ │ 553 | ╘════════════════════════════════════════════════════╛ 554 | ``` 555 | 556 | ## License ## 557 | 558 | MIT - http://chilts.mit-license.org/2014/ 559 | 560 | (Ends) 561 | -------------------------------------------------------------------------------- /mongodb-queue.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * mongodb-queue.js - Use your existing MongoDB as a local queue. 4 | * 5 | * Copyright (c) 2014 Andrew Chilton 6 | * - http://chilts.org/ 7 | * - andychilton@gmail.com 8 | * 9 | * License: http://chilts.mit-license.org/2014/ 10 | * 11 | **/ 12 | 13 | var crypto = require('crypto') 14 | 15 | // some helper functions 16 | function id() { 17 | return crypto.randomBytes(16).toString('hex') 18 | } 19 | 20 | function now() { 21 | return (new Date()).toISOString() 22 | } 23 | 24 | function nowPlusSecs(secs) { 25 | return (new Date(Date.now() + secs * 1000)).toISOString() 26 | } 27 | 28 | module.exports = function(db, name, opts) { 29 | return new Queue(db, name, opts) 30 | } 31 | 32 | // the Queue object itself 33 | function Queue(db, name, opts) { 34 | if ( !db ) { 35 | throw new Error("mongodb-queue: provide a mongodb.MongoClient.db") 36 | } 37 | if ( !name ) { 38 | throw new Error("mongodb-queue: provide a queue name") 39 | } 40 | opts = opts || {} 41 | 42 | this.db = db 43 | this.name = name 44 | this.col = db.collection(name) 45 | this.visibility = opts.visibility || 30 46 | this.delay = opts.delay || 0 47 | 48 | if ( opts.deadQueue ) { 49 | this.deadQueue = opts.deadQueue 50 | this.maxRetries = opts.maxRetries || 5 51 | } 52 | } 53 | 54 | Queue.prototype.createIndexes = function(callback) { 55 | var self = this 56 | 57 | self.col.createIndex({ deleted : 1, visible : 1 }, function(err, indexname) { 58 | if (err) return callback(err) 59 | self.col.createIndex({ ack : 1 }, { unique : true, sparse : true }, function(err) { 60 | if (err) return callback(err) 61 | callback(null, indexname) 62 | }) 63 | }) 64 | } 65 | 66 | Queue.prototype.add = function(payload, opts, callback) { 67 | var self = this 68 | if ( !callback ) { 69 | callback = opts 70 | opts = {} 71 | } 72 | var delay = opts.delay || self.delay 73 | var visible = delay ? (delay instanceof Date ? delay.toISOString() : nowPlusSecs(delay)) : now() 74 | 75 | var msgs = [] 76 | if (payload instanceof Array) { 77 | if (payload.length === 0) { 78 | var errMsg = 'Queue.add(): Array payload length must be greater than 0' 79 | return callback(new Error(errMsg)) 80 | } 81 | payload.forEach(function(payload) { 82 | msgs.push({ 83 | visible : visible, 84 | payload : payload, 85 | }) 86 | }) 87 | } else { 88 | msgs.push({ 89 | visible : visible, 90 | payload : payload, 91 | }) 92 | } 93 | 94 | self.col.insertMany(msgs, function(err, results) { 95 | if (err) return callback(err); 96 | if (payload instanceof Array) return callback(null, '' + results.insertedIds); 97 | callback(null, '' + results.insertedIds["0"]); 98 | }) 99 | } 100 | 101 | Queue.prototype.get = function(opts, callback) { 102 | var self = this 103 | if ( !callback ) { 104 | callback = opts 105 | opts = {} 106 | } 107 | 108 | var visibility = opts.visibility || self.visibility 109 | var query = { 110 | deleted : null, 111 | visible : { $lte : now() }, 112 | } 113 | var sort = { 114 | _id : 1 115 | } 116 | var update = { 117 | $inc : { tries : 1 }, 118 | $set : { 119 | ack : id(), 120 | visible : nowPlusSecs(visibility), 121 | } 122 | } 123 | 124 | self.col.findOneAndUpdate(query, update, { sort: sort, returnDocument : 'after' }, function(err, result) { 125 | if (err){ 126 | return callback(err); 127 | } 128 | 129 | var msg = result.value 130 | if (!msg) return callback() 131 | 132 | // convert to an external representation 133 | msg = { 134 | // convert '_id' to an 'id' string 135 | id : '' + msg._id, 136 | ack : msg.ack, 137 | payload : msg.payload, 138 | tries : msg.tries, 139 | } 140 | // if we have a deadQueue, then check the tries, else don't 141 | if ( self.deadQueue ) { 142 | // check the tries 143 | if ( msg.tries > self.maxRetries ) { 144 | // So: 145 | // 1) add this message to the deadQueue 146 | // 2) ack this message from the regular queue 147 | // 3) call ourself to return a new message (if exists) 148 | self.deadQueue.add(msg, function(err) { 149 | if (err) return callback(err) 150 | self.ack(msg.ack, function(err) { 151 | if (err) return callback(err) 152 | self.get(callback) 153 | }) 154 | }) 155 | return 156 | } 157 | } 158 | callback(null, msg) 159 | }) 160 | } 161 | 162 | Queue.prototype.ping = function(ack, opts, callback) { 163 | var self = this 164 | if ( !callback ) { 165 | callback = opts 166 | opts = {} 167 | } 168 | 169 | var visibility = opts.visibility || self.visibility 170 | var query = { 171 | ack : ack, 172 | visible : { $gt : now() }, 173 | deleted : null, 174 | } 175 | var update = { 176 | $set : { 177 | visible : nowPlusSecs(visibility) 178 | } 179 | } 180 | self.col.findOneAndUpdate(query, update, { returnDocument : 'after' }, function(err, msg, blah) { 181 | if (err) return callback(err) 182 | if ( !msg.value ) { 183 | return callback(new Error("Queue.ping(): Unidentified ack : " + ack)) 184 | } 185 | callback(null, '' + msg.value._id) 186 | }) 187 | } 188 | 189 | Queue.prototype.ack = function(ack, callback) { 190 | var self = this 191 | 192 | var query = { 193 | ack : ack, 194 | visible : { $gt : now() }, 195 | deleted : null, 196 | } 197 | var update = { 198 | $set : { 199 | deleted : now(), 200 | } 201 | } 202 | 203 | self.col.findOneAndUpdate(query, update, { returnDocument : 'after' }, function(err, msg, blah) { 204 | if (err) return callback(err) 205 | if ( !msg.value ) { 206 | return callback(new Error("Queue.ack(): Unidentified ack : " + ack)) 207 | } 208 | callback(null, '' + msg.value._id) 209 | }) 210 | } 211 | 212 | Queue.prototype.clean = function(callback) { 213 | var self = this 214 | 215 | var query = { 216 | deleted : { $exists : true }, 217 | } 218 | 219 | self.col.deleteMany(query, callback) 220 | } 221 | 222 | Queue.prototype.total = function(callback) { 223 | var self = this 224 | 225 | self.col.countDocuments(function(err, count) { 226 | if (err) return callback(err) 227 | callback(null, count) 228 | }) 229 | } 230 | 231 | Queue.prototype.size = function(callback) { 232 | var self = this 233 | 234 | var query = { 235 | deleted : null, 236 | visible : { $lte : now() }, 237 | } 238 | 239 | self.col.countDocuments(query, function(err, count) { 240 | if (err) return callback(err) 241 | callback(null, count) 242 | }) 243 | } 244 | 245 | Queue.prototype.inFlight = function(callback) { 246 | var self = this 247 | 248 | var query = { 249 | ack : { $exists : true }, 250 | visible : { $gt : now() }, 251 | deleted : null, 252 | } 253 | 254 | self.col.countDocuments(query, function(err, count) { 255 | if (err) return callback(err) 256 | callback(null, count) 257 | }) 258 | } 259 | 260 | Queue.prototype.done = function(callback) { 261 | var self = this 262 | 263 | var query = { 264 | deleted : { $exists : true }, 265 | } 266 | 267 | self.col.countDocuments(query, function(err, count) { 268 | if (err) return callback(err) 269 | callback(null, count) 270 | }) 271 | } 272 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mongodb-queue", 3 | "version": "4.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "version": "4.0.0", 9 | "license": "MIT", 10 | "devDependencies": { 11 | "async": "^2.6.2", 12 | "mongodb": "^4.0.0", 13 | "tape": "^4.10.1" 14 | } 15 | }, 16 | "node_modules/async": { 17 | "version": "2.6.2", 18 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", 19 | "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", 20 | "dev": true, 21 | "dependencies": { 22 | "lodash": "^4.17.11" 23 | } 24 | }, 25 | "node_modules/balanced-match": { 26 | "version": "1.0.0", 27 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 28 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 29 | "dev": true 30 | }, 31 | "node_modules/base64-js": { 32 | "version": "1.5.1", 33 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 34 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 35 | "dev": true, 36 | "funding": [ 37 | { 38 | "type": "github", 39 | "url": "https://github.com/sponsors/feross" 40 | }, 41 | { 42 | "type": "patreon", 43 | "url": "https://www.patreon.com/feross" 44 | }, 45 | { 46 | "type": "consulting", 47 | "url": "https://feross.org/support" 48 | } 49 | ] 50 | }, 51 | "node_modules/brace-expansion": { 52 | "version": "1.1.11", 53 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 54 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 55 | "dev": true, 56 | "dependencies": { 57 | "balanced-match": "^1.0.0", 58 | "concat-map": "0.0.1" 59 | } 60 | }, 61 | "node_modules/bson": { 62 | "version": "4.4.1", 63 | "resolved": "https://registry.npmjs.org/bson/-/bson-4.4.1.tgz", 64 | "integrity": "sha512-Uu4OCZa0jouQJCKOk1EmmyqtdWAP5HVLru4lQxTwzJzxT+sJ13lVpEZU/MATDxtHiekWMAL84oQY3Xn1LpJVSg==", 65 | "dev": true, 66 | "dependencies": { 67 | "buffer": "^5.6.0" 68 | }, 69 | "engines": { 70 | "node": ">=6.9.0" 71 | } 72 | }, 73 | "node_modules/buffer": { 74 | "version": "5.7.1", 75 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", 76 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 77 | "dev": true, 78 | "funding": [ 79 | { 80 | "type": "github", 81 | "url": "https://github.com/sponsors/feross" 82 | }, 83 | { 84 | "type": "patreon", 85 | "url": "https://www.patreon.com/feross" 86 | }, 87 | { 88 | "type": "consulting", 89 | "url": "https://feross.org/support" 90 | } 91 | ], 92 | "dependencies": { 93 | "base64-js": "^1.3.1", 94 | "ieee754": "^1.1.13" 95 | } 96 | }, 97 | "node_modules/concat-map": { 98 | "version": "0.0.1", 99 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 100 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 101 | "dev": true 102 | }, 103 | "node_modules/deep-equal": { 104 | "version": "1.0.1", 105 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", 106 | "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", 107 | "dev": true 108 | }, 109 | "node_modules/define-properties": { 110 | "version": "1.1.3", 111 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", 112 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", 113 | "dev": true, 114 | "dependencies": { 115 | "object-keys": "^1.0.12" 116 | }, 117 | "engines": { 118 | "node": ">= 0.4" 119 | } 120 | }, 121 | "node_modules/defined": { 122 | "version": "1.0.0", 123 | "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", 124 | "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", 125 | "dev": true 126 | }, 127 | "node_modules/denque": { 128 | "version": "1.5.0", 129 | "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", 130 | "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==", 131 | "dev": true, 132 | "engines": { 133 | "node": ">=0.10" 134 | } 135 | }, 136 | "node_modules/es-abstract": { 137 | "version": "1.13.0", 138 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", 139 | "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", 140 | "dev": true, 141 | "dependencies": { 142 | "es-to-primitive": "^1.2.0", 143 | "function-bind": "^1.1.1", 144 | "has": "^1.0.3", 145 | "is-callable": "^1.1.4", 146 | "is-regex": "^1.0.4", 147 | "object-keys": "^1.0.12" 148 | }, 149 | "engines": { 150 | "node": ">= 0.4" 151 | } 152 | }, 153 | "node_modules/es-to-primitive": { 154 | "version": "1.2.0", 155 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", 156 | "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", 157 | "dev": true, 158 | "dependencies": { 159 | "is-callable": "^1.1.4", 160 | "is-date-object": "^1.0.1", 161 | "is-symbol": "^1.0.2" 162 | }, 163 | "engines": { 164 | "node": ">= 0.4" 165 | } 166 | }, 167 | "node_modules/for-each": { 168 | "version": "0.3.3", 169 | "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", 170 | "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", 171 | "dev": true, 172 | "dependencies": { 173 | "is-callable": "^1.1.3" 174 | } 175 | }, 176 | "node_modules/fs.realpath": { 177 | "version": "1.0.0", 178 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 179 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 180 | "dev": true 181 | }, 182 | "node_modules/function-bind": { 183 | "version": "1.1.1", 184 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 185 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 186 | "dev": true 187 | }, 188 | "node_modules/glob": { 189 | "version": "7.1.3", 190 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", 191 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", 192 | "dev": true, 193 | "dependencies": { 194 | "fs.realpath": "^1.0.0", 195 | "inflight": "^1.0.4", 196 | "inherits": "2", 197 | "minimatch": "^3.0.4", 198 | "once": "^1.3.0", 199 | "path-is-absolute": "^1.0.0" 200 | }, 201 | "engines": { 202 | "node": "*" 203 | } 204 | }, 205 | "node_modules/has": { 206 | "version": "1.0.3", 207 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 208 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 209 | "dev": true, 210 | "dependencies": { 211 | "function-bind": "^1.1.1" 212 | }, 213 | "engines": { 214 | "node": ">= 0.4.0" 215 | } 216 | }, 217 | "node_modules/has-symbols": { 218 | "version": "1.0.0", 219 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", 220 | "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", 221 | "dev": true, 222 | "engines": { 223 | "node": ">= 0.4" 224 | } 225 | }, 226 | "node_modules/ieee754": { 227 | "version": "1.2.1", 228 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 229 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", 230 | "dev": true, 231 | "funding": [ 232 | { 233 | "type": "github", 234 | "url": "https://github.com/sponsors/feross" 235 | }, 236 | { 237 | "type": "patreon", 238 | "url": "https://www.patreon.com/feross" 239 | }, 240 | { 241 | "type": "consulting", 242 | "url": "https://feross.org/support" 243 | } 244 | ] 245 | }, 246 | "node_modules/inflight": { 247 | "version": "1.0.6", 248 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 249 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 250 | "dev": true, 251 | "dependencies": { 252 | "once": "^1.3.0", 253 | "wrappy": "1" 254 | } 255 | }, 256 | "node_modules/inherits": { 257 | "version": "2.0.3", 258 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 259 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 260 | "dev": true 261 | }, 262 | "node_modules/is-callable": { 263 | "version": "1.1.4", 264 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", 265 | "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", 266 | "dev": true, 267 | "engines": { 268 | "node": ">= 0.4" 269 | } 270 | }, 271 | "node_modules/is-date-object": { 272 | "version": "1.0.1", 273 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", 274 | "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", 275 | "dev": true, 276 | "engines": { 277 | "node": ">= 0.4" 278 | } 279 | }, 280 | "node_modules/is-regex": { 281 | "version": "1.0.4", 282 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", 283 | "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", 284 | "dev": true, 285 | "dependencies": { 286 | "has": "^1.0.1" 287 | }, 288 | "engines": { 289 | "node": ">= 0.4" 290 | } 291 | }, 292 | "node_modules/is-symbol": { 293 | "version": "1.0.2", 294 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", 295 | "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", 296 | "dev": true, 297 | "dependencies": { 298 | "has-symbols": "^1.0.0" 299 | }, 300 | "engines": { 301 | "node": ">= 0.4" 302 | } 303 | }, 304 | "node_modules/lodash": { 305 | "version": "4.17.21", 306 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 307 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 308 | "dev": true 309 | }, 310 | "node_modules/memory-pager": { 311 | "version": "1.5.0", 312 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", 313 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", 314 | "dev": true, 315 | "optional": true 316 | }, 317 | "node_modules/minimatch": { 318 | "version": "3.0.4", 319 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 320 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 321 | "dev": true, 322 | "dependencies": { 323 | "brace-expansion": "^1.1.7" 324 | }, 325 | "engines": { 326 | "node": "*" 327 | } 328 | }, 329 | "node_modules/minimist": { 330 | "version": "1.2.5", 331 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 332 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", 333 | "dev": true 334 | }, 335 | "node_modules/mongodb": { 336 | "version": "4.0.1", 337 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.0.1.tgz", 338 | "integrity": "sha512-Ll2YCciRgbFN2jdfSqW1vhxvAcnqu+5ZlrTZNaEg+hZqKREg4xiUV56ZAtTjC02skfoTirHY5jQwtg7mBxqfug==", 339 | "dev": true, 340 | "dependencies": { 341 | "bson": "^4.4.0", 342 | "denque": "^1.5.0", 343 | "mongodb-connection-string-url": "^1.0.1" 344 | }, 345 | "engines": { 346 | "node": ">=12.9.0" 347 | }, 348 | "optionalDependencies": { 349 | "saslprep": "^1.0.0" 350 | } 351 | }, 352 | "node_modules/mongodb-connection-string-url": { 353 | "version": "1.0.1", 354 | "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-1.0.1.tgz", 355 | "integrity": "sha512-sXi8w9nwbMrErWbOK+8nofHz531rboasDbYAMS+sQ+W+2YnHN980RlMr+t5SDL6uKEU/kw/wG6jcjCTLiJltoA==", 356 | "dev": true, 357 | "dependencies": { 358 | "whatwg-url": "^8.4.0" 359 | } 360 | }, 361 | "node_modules/object-inspect": { 362 | "version": "1.6.0", 363 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", 364 | "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", 365 | "dev": true 366 | }, 367 | "node_modules/object-keys": { 368 | "version": "1.1.0", 369 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz", 370 | "integrity": "sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==", 371 | "dev": true, 372 | "engines": { 373 | "node": ">= 0.4" 374 | } 375 | }, 376 | "node_modules/once": { 377 | "version": "1.4.0", 378 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 379 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 380 | "dev": true, 381 | "dependencies": { 382 | "wrappy": "1" 383 | } 384 | }, 385 | "node_modules/path-is-absolute": { 386 | "version": "1.0.1", 387 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 388 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 389 | "dev": true, 390 | "engines": { 391 | "node": ">=0.10.0" 392 | } 393 | }, 394 | "node_modules/path-parse": { 395 | "version": "1.0.6", 396 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 397 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 398 | "dev": true 399 | }, 400 | "node_modules/punycode": { 401 | "version": "2.1.1", 402 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 403 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 404 | "dev": true, 405 | "engines": { 406 | "node": ">=6" 407 | } 408 | }, 409 | "node_modules/resolve": { 410 | "version": "1.10.0", 411 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", 412 | "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", 413 | "dev": true, 414 | "dependencies": { 415 | "path-parse": "^1.0.6" 416 | } 417 | }, 418 | "node_modules/resumer": { 419 | "version": "0.0.0", 420 | "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", 421 | "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", 422 | "dev": true, 423 | "dependencies": { 424 | "through": "~2.3.4" 425 | } 426 | }, 427 | "node_modules/saslprep": { 428 | "version": "1.0.3", 429 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", 430 | "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", 431 | "dev": true, 432 | "optional": true, 433 | "dependencies": { 434 | "sparse-bitfield": "^3.0.3" 435 | }, 436 | "engines": { 437 | "node": ">=6" 438 | } 439 | }, 440 | "node_modules/sparse-bitfield": { 441 | "version": "3.0.3", 442 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", 443 | "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", 444 | "dev": true, 445 | "optional": true, 446 | "dependencies": { 447 | "memory-pager": "^1.0.2" 448 | } 449 | }, 450 | "node_modules/string.prototype.trim": { 451 | "version": "1.1.2", 452 | "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz", 453 | "integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=", 454 | "dev": true, 455 | "dependencies": { 456 | "define-properties": "^1.1.2", 457 | "es-abstract": "^1.5.0", 458 | "function-bind": "^1.0.2" 459 | }, 460 | "engines": { 461 | "node": ">= 0.4" 462 | } 463 | }, 464 | "node_modules/tape": { 465 | "version": "4.10.1", 466 | "resolved": "https://registry.npmjs.org/tape/-/tape-4.10.1.tgz", 467 | "integrity": "sha512-G0DywYV1jQeY3axeYnXUOt6ktnxS9OPJh97FGR3nrua8lhWi1zPflLxcAHavZ7Jf3qUfY7cxcVIVFa4mY2IY1w==", 468 | "dev": true, 469 | "dependencies": { 470 | "deep-equal": "~1.0.1", 471 | "defined": "~1.0.0", 472 | "for-each": "~0.3.3", 473 | "function-bind": "~1.1.1", 474 | "glob": "~7.1.3", 475 | "has": "~1.0.3", 476 | "inherits": "~2.0.3", 477 | "minimist": "~1.2.0", 478 | "object-inspect": "~1.6.0", 479 | "resolve": "~1.10.0", 480 | "resumer": "~0.0.0", 481 | "string.prototype.trim": "~1.1.2", 482 | "through": "~2.3.8" 483 | }, 484 | "bin": { 485 | "tape": "bin/tape" 486 | } 487 | }, 488 | "node_modules/through": { 489 | "version": "2.3.8", 490 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 491 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 492 | "dev": true 493 | }, 494 | "node_modules/tr46": { 495 | "version": "2.1.0", 496 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", 497 | "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", 498 | "dev": true, 499 | "dependencies": { 500 | "punycode": "^2.1.1" 501 | }, 502 | "engines": { 503 | "node": ">=8" 504 | } 505 | }, 506 | "node_modules/webidl-conversions": { 507 | "version": "6.1.0", 508 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", 509 | "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", 510 | "dev": true, 511 | "engines": { 512 | "node": ">=10.4" 513 | } 514 | }, 515 | "node_modules/whatwg-url": { 516 | "version": "8.7.0", 517 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", 518 | "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", 519 | "dev": true, 520 | "dependencies": { 521 | "lodash": "^4.7.0", 522 | "tr46": "^2.1.0", 523 | "webidl-conversions": "^6.1.0" 524 | }, 525 | "engines": { 526 | "node": ">=10" 527 | } 528 | }, 529 | "node_modules/wrappy": { 530 | "version": "1.0.2", 531 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 532 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 533 | "dev": true 534 | } 535 | }, 536 | "dependencies": { 537 | "async": { 538 | "version": "2.6.2", 539 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", 540 | "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", 541 | "dev": true, 542 | "requires": { 543 | "lodash": "^4.17.11" 544 | } 545 | }, 546 | "balanced-match": { 547 | "version": "1.0.0", 548 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 549 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 550 | "dev": true 551 | }, 552 | "base64-js": { 553 | "version": "1.5.1", 554 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 555 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 556 | "dev": true 557 | }, 558 | "brace-expansion": { 559 | "version": "1.1.11", 560 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 561 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 562 | "dev": true, 563 | "requires": { 564 | "balanced-match": "^1.0.0", 565 | "concat-map": "0.0.1" 566 | } 567 | }, 568 | "bson": { 569 | "version": "4.4.1", 570 | "resolved": "https://registry.npmjs.org/bson/-/bson-4.4.1.tgz", 571 | "integrity": "sha512-Uu4OCZa0jouQJCKOk1EmmyqtdWAP5HVLru4lQxTwzJzxT+sJ13lVpEZU/MATDxtHiekWMAL84oQY3Xn1LpJVSg==", 572 | "dev": true, 573 | "requires": { 574 | "buffer": "^5.6.0" 575 | } 576 | }, 577 | "buffer": { 578 | "version": "5.7.1", 579 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", 580 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 581 | "dev": true, 582 | "requires": { 583 | "base64-js": "^1.3.1", 584 | "ieee754": "^1.1.13" 585 | } 586 | }, 587 | "concat-map": { 588 | "version": "0.0.1", 589 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 590 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 591 | "dev": true 592 | }, 593 | "deep-equal": { 594 | "version": "1.0.1", 595 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", 596 | "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", 597 | "dev": true 598 | }, 599 | "define-properties": { 600 | "version": "1.1.3", 601 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", 602 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", 603 | "dev": true, 604 | "requires": { 605 | "object-keys": "^1.0.12" 606 | } 607 | }, 608 | "defined": { 609 | "version": "1.0.0", 610 | "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", 611 | "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", 612 | "dev": true 613 | }, 614 | "denque": { 615 | "version": "1.5.0", 616 | "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", 617 | "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==", 618 | "dev": true 619 | }, 620 | "es-abstract": { 621 | "version": "1.13.0", 622 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", 623 | "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", 624 | "dev": true, 625 | "requires": { 626 | "es-to-primitive": "^1.2.0", 627 | "function-bind": "^1.1.1", 628 | "has": "^1.0.3", 629 | "is-callable": "^1.1.4", 630 | "is-regex": "^1.0.4", 631 | "object-keys": "^1.0.12" 632 | } 633 | }, 634 | "es-to-primitive": { 635 | "version": "1.2.0", 636 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", 637 | "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", 638 | "dev": true, 639 | "requires": { 640 | "is-callable": "^1.1.4", 641 | "is-date-object": "^1.0.1", 642 | "is-symbol": "^1.0.2" 643 | } 644 | }, 645 | "for-each": { 646 | "version": "0.3.3", 647 | "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", 648 | "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", 649 | "dev": true, 650 | "requires": { 651 | "is-callable": "^1.1.3" 652 | } 653 | }, 654 | "fs.realpath": { 655 | "version": "1.0.0", 656 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 657 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 658 | "dev": true 659 | }, 660 | "function-bind": { 661 | "version": "1.1.1", 662 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 663 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 664 | "dev": true 665 | }, 666 | "glob": { 667 | "version": "7.1.3", 668 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", 669 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", 670 | "dev": true, 671 | "requires": { 672 | "fs.realpath": "^1.0.0", 673 | "inflight": "^1.0.4", 674 | "inherits": "2", 675 | "minimatch": "^3.0.4", 676 | "once": "^1.3.0", 677 | "path-is-absolute": "^1.0.0" 678 | } 679 | }, 680 | "has": { 681 | "version": "1.0.3", 682 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 683 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 684 | "dev": true, 685 | "requires": { 686 | "function-bind": "^1.1.1" 687 | } 688 | }, 689 | "has-symbols": { 690 | "version": "1.0.0", 691 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", 692 | "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", 693 | "dev": true 694 | }, 695 | "ieee754": { 696 | "version": "1.2.1", 697 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 698 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", 699 | "dev": true 700 | }, 701 | "inflight": { 702 | "version": "1.0.6", 703 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 704 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 705 | "dev": true, 706 | "requires": { 707 | "once": "^1.3.0", 708 | "wrappy": "1" 709 | } 710 | }, 711 | "inherits": { 712 | "version": "2.0.3", 713 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 714 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 715 | "dev": true 716 | }, 717 | "is-callable": { 718 | "version": "1.1.4", 719 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", 720 | "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", 721 | "dev": true 722 | }, 723 | "is-date-object": { 724 | "version": "1.0.1", 725 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", 726 | "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", 727 | "dev": true 728 | }, 729 | "is-regex": { 730 | "version": "1.0.4", 731 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", 732 | "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", 733 | "dev": true, 734 | "requires": { 735 | "has": "^1.0.1" 736 | } 737 | }, 738 | "is-symbol": { 739 | "version": "1.0.2", 740 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", 741 | "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", 742 | "dev": true, 743 | "requires": { 744 | "has-symbols": "^1.0.0" 745 | } 746 | }, 747 | "lodash": { 748 | "version": "4.17.21", 749 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 750 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 751 | "dev": true 752 | }, 753 | "memory-pager": { 754 | "version": "1.5.0", 755 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", 756 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", 757 | "dev": true, 758 | "optional": true 759 | }, 760 | "minimatch": { 761 | "version": "3.0.4", 762 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 763 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 764 | "dev": true, 765 | "requires": { 766 | "brace-expansion": "^1.1.7" 767 | } 768 | }, 769 | "minimist": { 770 | "version": "1.2.5", 771 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 772 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", 773 | "dev": true 774 | }, 775 | "mongodb": { 776 | "version": "4.0.1", 777 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.0.1.tgz", 778 | "integrity": "sha512-Ll2YCciRgbFN2jdfSqW1vhxvAcnqu+5ZlrTZNaEg+hZqKREg4xiUV56ZAtTjC02skfoTirHY5jQwtg7mBxqfug==", 779 | "dev": true, 780 | "requires": { 781 | "bson": "^4.4.0", 782 | "denque": "^1.5.0", 783 | "mongodb-connection-string-url": "^1.0.1", 784 | "saslprep": "^1.0.0" 785 | } 786 | }, 787 | "mongodb-connection-string-url": { 788 | "version": "1.0.1", 789 | "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-1.0.1.tgz", 790 | "integrity": "sha512-sXi8w9nwbMrErWbOK+8nofHz531rboasDbYAMS+sQ+W+2YnHN980RlMr+t5SDL6uKEU/kw/wG6jcjCTLiJltoA==", 791 | "dev": true, 792 | "requires": { 793 | "whatwg-url": "^8.4.0" 794 | } 795 | }, 796 | "object-inspect": { 797 | "version": "1.6.0", 798 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", 799 | "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", 800 | "dev": true 801 | }, 802 | "object-keys": { 803 | "version": "1.1.0", 804 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz", 805 | "integrity": "sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==", 806 | "dev": true 807 | }, 808 | "once": { 809 | "version": "1.4.0", 810 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 811 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 812 | "dev": true, 813 | "requires": { 814 | "wrappy": "1" 815 | } 816 | }, 817 | "path-is-absolute": { 818 | "version": "1.0.1", 819 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 820 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 821 | "dev": true 822 | }, 823 | "path-parse": { 824 | "version": "1.0.6", 825 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 826 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 827 | "dev": true 828 | }, 829 | "punycode": { 830 | "version": "2.1.1", 831 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 832 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 833 | "dev": true 834 | }, 835 | "resolve": { 836 | "version": "1.10.0", 837 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", 838 | "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", 839 | "dev": true, 840 | "requires": { 841 | "path-parse": "^1.0.6" 842 | } 843 | }, 844 | "resumer": { 845 | "version": "0.0.0", 846 | "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", 847 | "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", 848 | "dev": true, 849 | "requires": { 850 | "through": "~2.3.4" 851 | } 852 | }, 853 | "saslprep": { 854 | "version": "1.0.3", 855 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", 856 | "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", 857 | "dev": true, 858 | "optional": true, 859 | "requires": { 860 | "sparse-bitfield": "^3.0.3" 861 | } 862 | }, 863 | "sparse-bitfield": { 864 | "version": "3.0.3", 865 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", 866 | "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", 867 | "dev": true, 868 | "optional": true, 869 | "requires": { 870 | "memory-pager": "^1.0.2" 871 | } 872 | }, 873 | "string.prototype.trim": { 874 | "version": "1.1.2", 875 | "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz", 876 | "integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=", 877 | "dev": true, 878 | "requires": { 879 | "define-properties": "^1.1.2", 880 | "es-abstract": "^1.5.0", 881 | "function-bind": "^1.0.2" 882 | } 883 | }, 884 | "tape": { 885 | "version": "4.10.1", 886 | "resolved": "https://registry.npmjs.org/tape/-/tape-4.10.1.tgz", 887 | "integrity": "sha512-G0DywYV1jQeY3axeYnXUOt6ktnxS9OPJh97FGR3nrua8lhWi1zPflLxcAHavZ7Jf3qUfY7cxcVIVFa4mY2IY1w==", 888 | "dev": true, 889 | "requires": { 890 | "deep-equal": "~1.0.1", 891 | "defined": "~1.0.0", 892 | "for-each": "~0.3.3", 893 | "function-bind": "~1.1.1", 894 | "glob": "~7.1.3", 895 | "has": "~1.0.3", 896 | "inherits": "~2.0.3", 897 | "minimist": "~1.2.0", 898 | "object-inspect": "~1.6.0", 899 | "resolve": "~1.10.0", 900 | "resumer": "~0.0.0", 901 | "string.prototype.trim": "~1.1.2", 902 | "through": "~2.3.8" 903 | } 904 | }, 905 | "through": { 906 | "version": "2.3.8", 907 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 908 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 909 | "dev": true 910 | }, 911 | "tr46": { 912 | "version": "2.1.0", 913 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", 914 | "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", 915 | "dev": true, 916 | "requires": { 917 | "punycode": "^2.1.1" 918 | } 919 | }, 920 | "webidl-conversions": { 921 | "version": "6.1.0", 922 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", 923 | "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", 924 | "dev": true 925 | }, 926 | "whatwg-url": { 927 | "version": "8.7.0", 928 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", 929 | "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", 930 | "dev": true, 931 | "requires": { 932 | "lodash": "^4.7.0", 933 | "tr46": "^2.1.0", 934 | "webidl-conversions": "^6.1.0" 935 | } 936 | }, 937 | "wrappy": { 938 | "version": "1.0.2", 939 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 940 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 941 | "dev": true 942 | } 943 | } 944 | } 945 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mongodb-queue", 3 | "version": "5.0.0", 4 | "description": "Message queues which uses MongoDB.", 5 | "main": "mongodb-queue.js", 6 | "scripts": { 7 | "test": "set -e; for FILE in test/*.js; do echo --- $FILE ---; node $FILE; done" 8 | }, 9 | "dependencies": {}, 10 | "devDependencies": { 11 | "async": "^2.6.2", 12 | "mongodb": "^4.0.0", 13 | "tape": "^4.10.1" 14 | }, 15 | "homepage": "https://github.com/chilts/mongodb-queue", 16 | "repository": { 17 | "type": "git", 18 | "url": "git://github.com/chilts/mongodb-queue.git" 19 | }, 20 | "bugs": { 21 | "url": "http://github.com/chilts/mongodb-queue/issues", 22 | "mail": "andychilton@gmail.com" 23 | }, 24 | "author": { 25 | "name": "Andrew Chilton", 26 | "email": "andychilton@gmail.com", 27 | "url": "http://chilts.org/" 28 | }, 29 | "license": "MIT", 30 | "keywords": [ 31 | "mongodb", 32 | "queue" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /test/clean.js: -------------------------------------------------------------------------------- 1 | var async = require('async') 2 | var test = require('tape') 3 | 4 | var setup = require('./setup.js') 5 | var mongoDbQueue = require('../') 6 | 7 | setup(function(client, db) { 8 | 9 | test('clean: check deleted messages are deleted', function(t) { 10 | var queue = mongoDbQueue(db, 'clean', { visibility : 3 }) 11 | var msg 12 | 13 | async.series( 14 | [ 15 | function(next) { 16 | queue.size(function(err, size) { 17 | t.ok(!err, 'There is no error.') 18 | t.equal(size, 0, 'There is currently nothing on the queue') 19 | next() 20 | }) 21 | }, 22 | function(next) { 23 | queue.total(function(err, size) { 24 | t.ok(!err, 'There is no error.') 25 | t.equal(size, 0, 'There is currently nothing in the queue at all') 26 | next() 27 | }) 28 | }, 29 | function(next) { 30 | queue.clean(function(err) { 31 | t.ok(!err, 'There is no error.') 32 | next() 33 | }) 34 | }, 35 | function(next) { 36 | queue.size(function(err, size) { 37 | t.ok(!err, 'There is no error.') 38 | t.equal(size, 0, 'There is currently nothing on the queue') 39 | next() 40 | }) 41 | }, 42 | function(next) { 43 | queue.total(function(err, size) { 44 | t.ok(!err, 'There is no error.') 45 | t.equal(size, 0, 'There is currently nothing in the queue at all') 46 | next() 47 | }) 48 | }, 49 | function(next) { 50 | queue.add('Hello, World!', function(err) { 51 | t.ok(!err, 'There is no error when adding a message.') 52 | next() 53 | }) 54 | }, 55 | function(next) { 56 | queue.clean(function(err) { 57 | t.ok(!err, 'There is no error.') 58 | next() 59 | }) 60 | }, 61 | function(next) { 62 | queue.size(function(err, size) { 63 | t.ok(!err, 'There is no error.') 64 | t.equal(size, 1, 'Queue size is correct') 65 | next() 66 | }) 67 | }, 68 | function(next) { 69 | queue.total(function(err, size) { 70 | t.ok(!err, 'There is no error.') 71 | t.equal(size, 1, 'Queue total is correct') 72 | next() 73 | }) 74 | }, 75 | function(next) { 76 | queue.get(function(err, newMsg) { 77 | msg = newMsg 78 | t.ok(msg.id, 'Got a msg.id (sanity check)') 79 | next() 80 | }) 81 | }, 82 | function(next) { 83 | queue.size(function(err, size) { 84 | t.ok(!err, 'There is no error.') 85 | t.equal(size, 0, 'Queue size is correct') 86 | next() 87 | }) 88 | }, 89 | function(next) { 90 | queue.total(function(err, size) { 91 | t.ok(!err, 'There is no error.') 92 | t.equal(size, 1, 'Queue total is correct') 93 | next() 94 | }) 95 | }, 96 | function(next) { 97 | queue.clean(function(err) { 98 | t.ok(!err, 'There is no error.') 99 | next() 100 | }) 101 | }, 102 | function(next) { 103 | queue.size(function(err, size) { 104 | t.ok(!err, 'There is no error.') 105 | t.equal(size, 0, 'Queue size is correct') 106 | next() 107 | }) 108 | }, 109 | function(next) { 110 | queue.total(function(err, size) { 111 | t.ok(!err, 'There is no error.') 112 | t.equal(size, 1, 'Queue total is correct') 113 | next() 114 | }) 115 | }, 116 | function(next) { 117 | queue.ack(msg.ack, function(err, id) { 118 | t.ok(!err, 'No error when acking the message') 119 | t.ok(id, 'Received an id when acking this message') 120 | next() 121 | }) 122 | }, 123 | function(next) { 124 | queue.size(function(err, size) { 125 | t.ok(!err, 'There is no error.') 126 | t.equal(size, 0, 'Queue size is correct') 127 | next() 128 | }) 129 | }, 130 | function(next) { 131 | queue.total(function(err, size) { 132 | t.ok(!err, 'There is no error.') 133 | t.equal(size, 1, 'Queue total is correct') 134 | next() 135 | }) 136 | }, 137 | function(next) { 138 | queue.clean(function(err) { 139 | t.ok(!err, 'There is no error.') 140 | next() 141 | }) 142 | }, 143 | function(next) { 144 | queue.size(function(err, size) { 145 | t.ok(!err, 'There is no error.') 146 | t.equal(size, 0, 'Queue size is correct') 147 | next() 148 | }) 149 | }, 150 | function(next) { 151 | queue.total(function(err, size) { 152 | t.ok(!err, 'There is no error.') 153 | t.equal(size, 0, 'Queue total is correct') 154 | next() 155 | }) 156 | }, 157 | ], 158 | function(err) { 159 | if (err) t.fail(err) 160 | t.pass('Finished test ok') 161 | t.end() 162 | } 163 | ) 164 | }) 165 | 166 | test('client.close()', function(t) { 167 | t.pass('client.close()') 168 | client.close() 169 | t.end() 170 | }) 171 | 172 | }) 173 | -------------------------------------------------------------------------------- /test/dead-queue.js: -------------------------------------------------------------------------------- 1 | var async = require('async') 2 | var test = require('tape') 3 | 4 | var setup = require('./setup.js') 5 | var mongoDbQueue = require('../') 6 | 7 | setup(function(client, db) { 8 | 9 | test('first test', function(t) { 10 | var queue = mongoDbQueue(db, 'queue', { visibility : 3, deadQueue : 'dead-queue' }) 11 | t.ok(queue, 'Queue created ok') 12 | t.end() 13 | }); 14 | 15 | test('single message going over 5 tries, should appear on dead-queue', function(t) { 16 | var deadQueue = mongoDbQueue(db, 'dead-queue') 17 | var queue = mongoDbQueue(db, 'queue', { visibility : 1, deadQueue : deadQueue }) 18 | var msg 19 | var origId 20 | 21 | async.series( 22 | [ 23 | function(next) { 24 | queue.add('Hello, World!', function(err, id) { 25 | t.ok(!err, 'There is no error when adding a message.') 26 | t.ok(id, 'Received an id for this message') 27 | origId = id 28 | next() 29 | }) 30 | }, 31 | function(next) { 32 | queue.get(function(err, thisMsg) { 33 | setTimeout(function() { 34 | t.pass('First expiration') 35 | next() 36 | }, 2 * 1000) 37 | }) 38 | }, 39 | function(next) { 40 | queue.get(function(err, thisMsg) { 41 | setTimeout(function() { 42 | t.pass('Second expiration') 43 | next() 44 | }, 2 * 1000) 45 | }) 46 | }, 47 | function(next) { 48 | queue.get(function(err, thisMsg) { 49 | setTimeout(function() { 50 | t.pass('Third expiration') 51 | next() 52 | }, 2 * 1000) 53 | }) 54 | }, 55 | function(next) { 56 | queue.get(function(err, thisMsg) { 57 | setTimeout(function() { 58 | t.pass('Fourth expiration') 59 | next() 60 | }, 2 * 1000) 61 | }) 62 | }, 63 | function(next) { 64 | queue.get(function(err, thisMsg) { 65 | setTimeout(function() { 66 | t.pass('Fifth expiration') 67 | next() 68 | }, 2 * 1000) 69 | }) 70 | }, 71 | function(next) { 72 | queue.get(function(err, id) { 73 | t.ok(!err, 'No error when getting no messages') 74 | t.ok(!msg, 'No msg received') 75 | next() 76 | }) 77 | }, 78 | function(next) { 79 | deadQueue.get(function(err, msg) { 80 | t.ok(!err, 'No error when getting from the deadQueue') 81 | t.ok(msg.id, 'Got a message id from the deadQueue') 82 | t.equal(msg.payload.id, origId, 'Got the same message id as the original message') 83 | t.equal(msg.payload.payload, 'Hello, World!', 'Got the same as the original message') 84 | t.equal(msg.payload.tries, 6, 'Got the tries as 6') 85 | next() 86 | }) 87 | }, 88 | ], 89 | function(err) { 90 | t.ok(!err, 'No error during single round-trip test') 91 | t.end() 92 | } 93 | ) 94 | }) 95 | 96 | test('two messages, with first going over 3 tries', function(t) { 97 | var deadQueue = mongoDbQueue(db, 'dead-queue-2') 98 | var queue = mongoDbQueue(db, 'queue-2', { visibility : 1, deadQueue : deadQueue, maxRetries : 3 }) 99 | var msg 100 | var origId, origId2 101 | 102 | async.series( 103 | [ 104 | function(next) { 105 | queue.add('Hello, World!', function(err, id) { 106 | t.ok(!err, 'There is no error when adding a message.') 107 | t.ok(id, 'Received an id for this message') 108 | origId = id 109 | next() 110 | }) 111 | }, 112 | function(next) { 113 | queue.add('Part II', function(err, id) { 114 | t.ok(!err, 'There is no error when adding another message.') 115 | t.ok(id, 'Received an id for this message') 116 | origId2 = id 117 | next() 118 | }) 119 | }, 120 | function(next) { 121 | queue.get(function(err, thisMsg) { 122 | t.equal(thisMsg.id, origId, 'We return the first message on first go') 123 | setTimeout(function() { 124 | t.pass('First expiration') 125 | next() 126 | }, 2 * 1000) 127 | }) 128 | }, 129 | function(next) { 130 | queue.get(function(err, thisMsg) { 131 | t.equal(thisMsg.id, origId, 'We return the first message on second go') 132 | setTimeout(function() { 133 | t.pass('Second expiration') 134 | next() 135 | }, 2 * 1000) 136 | }) 137 | }, 138 | function(next) { 139 | queue.get(function(err, thisMsg) { 140 | t.equal(thisMsg.id, origId, 'We return the first message on third go') 141 | setTimeout(function() { 142 | t.pass('Third expiration') 143 | next() 144 | }, 2 * 1000) 145 | }) 146 | }, 147 | function(next) { 148 | // This is the 4th time, so we SHOULD have moved it to the dead queue 149 | // pior to it being returned. 150 | queue.get(function(err, msg) { 151 | t.ok(!err, 'No error when getting the 2nd message') 152 | t.equal(msg.id, origId2, 'Got the ID of the 2nd message') 153 | t.equal(msg.payload, 'Part II', 'Got the same payload as the 2nd message') 154 | next() 155 | }) 156 | }, 157 | function(next) { 158 | deadQueue.get(function(err, msg) { 159 | t.ok(!err, 'No error when getting from the deadQueue') 160 | t.ok(msg.id, 'Got a message id from the deadQueue') 161 | t.equal(msg.payload.id, origId, 'Got the same message id as the original message') 162 | t.equal(msg.payload.payload, 'Hello, World!', 'Got the same as the original message') 163 | t.equal(msg.payload.tries, 4, 'Got the tries as 4') 164 | next() 165 | }) 166 | }, 167 | ], 168 | function(err) { 169 | t.ok(!err, 'No error during single round-trip test') 170 | t.end() 171 | } 172 | ) 173 | }) 174 | 175 | test('client.close()', function(t) { 176 | t.pass('client.close()') 177 | client.close() 178 | t.end() 179 | }) 180 | 181 | }) 182 | -------------------------------------------------------------------------------- /test/default.js: -------------------------------------------------------------------------------- 1 | var async = require('async') 2 | var test = require('tape') 3 | 4 | var setup = require('./setup.js') 5 | var mongoDbQueue = require('../') 6 | 7 | setup(function(client, db) { 8 | 9 | test('first test', function(t) { 10 | var queue = mongoDbQueue(db, 'default') 11 | t.ok(queue, 'Queue created ok') 12 | t.end() 13 | }); 14 | 15 | test('single round trip', function(t) { 16 | var queue = mongoDbQueue(db, 'default') 17 | var msg 18 | 19 | async.series( 20 | [ 21 | function(next) { 22 | queue.add('Hello, World!', function(err, id) { 23 | t.ok(!err, 'There is no error when adding a message.') 24 | t.ok(id, 'Received an id for this message') 25 | next() 26 | }) 27 | }, 28 | function(next) { 29 | queue.get(function(err, thisMsg) { 30 | console.log(thisMsg) 31 | msg = thisMsg 32 | t.ok(msg.id, 'Got a msg.id') 33 | t.equal(typeof msg.id, 'string', 'msg.id is a string') 34 | t.ok(msg.ack, 'Got a msg.ack') 35 | t.equal(typeof msg.ack, 'string', 'msg.ack is a string') 36 | t.ok(msg.tries, 'Got a msg.tries') 37 | t.equal(typeof msg.tries, 'number', 'msg.tries is a number') 38 | t.equal(msg.tries, 1, 'msg.tries is currently one') 39 | t.equal(msg.payload, 'Hello, World!', 'Payload is correct') 40 | next() 41 | }) 42 | }, 43 | function(next) { 44 | queue.ack(msg.ack, function(err, id) { 45 | t.ok(!err, 'No error when acking the message') 46 | t.ok(id, 'Received an id when acking this message') 47 | next() 48 | }) 49 | }, 50 | ], 51 | function(err) { 52 | t.ok(!err, 'No error during single round-trip test') 53 | t.end() 54 | } 55 | ) 56 | }) 57 | 58 | test("single round trip, can't be acked again", function(t) { 59 | var queue = mongoDbQueue(db, 'default') 60 | var msg 61 | 62 | async.series( 63 | [ 64 | function(next) { 65 | queue.add('Hello, World!', function(err, id) { 66 | t.ok(!err, 'There is no error when adding a message.') 67 | t.ok(id, 'Received an id for this message') 68 | next() 69 | }) 70 | }, 71 | function(next) { 72 | queue.get(function(err, thisMsg) { 73 | msg = thisMsg 74 | t.ok(msg.id, 'Got a msg.id') 75 | t.equal(typeof msg.id, 'string', 'msg.id is a string') 76 | t.ok(msg.ack, 'Got a msg.ack') 77 | t.equal(typeof msg.ack, 'string', 'msg.ack is a string') 78 | t.ok(msg.tries, 'Got a msg.tries') 79 | t.equal(typeof msg.tries, 'number', 'msg.tries is a number') 80 | t.equal(msg.tries, 1, 'msg.tries is currently one') 81 | t.equal(msg.payload, 'Hello, World!', 'Payload is correct') 82 | next() 83 | }) 84 | }, 85 | function(next) { 86 | queue.ack(msg.ack, function(err, id) { 87 | t.ok(!err, 'No error when acking the message') 88 | t.ok(id, 'Received an id when acking this message') 89 | next() 90 | }) 91 | }, 92 | function(next) { 93 | queue.ack(msg.ack, function(err, id) { 94 | t.ok(err, 'There is an error when acking the message again') 95 | t.ok(!id, 'No id received when trying to ack an already deleted message') 96 | next() 97 | }) 98 | }, 99 | ], 100 | function(err) { 101 | t.ok(!err, 'No error during single round-trip when trying to double ack') 102 | t.end() 103 | } 104 | ) 105 | }) 106 | 107 | test('client.close()', function(t) { 108 | t.pass('client.close()') 109 | client.close() 110 | t.end() 111 | }) 112 | 113 | }) 114 | -------------------------------------------------------------------------------- /test/delay.js: -------------------------------------------------------------------------------- 1 | var async = require('async') 2 | var test = require('tape') 3 | 4 | var setup = require('./setup.js') 5 | var mongoDbQueue = require('../') 6 | 7 | setup(function(client, db) { 8 | 9 | test('delay: check messages on this queue are returned after the delay', function(t) { 10 | var queue = mongoDbQueue(db, 'delay', { delay : 3 }) 11 | 12 | async.series( 13 | [ 14 | function(next) { 15 | queue.add('Hello, World!', function(err, id) { 16 | t.ok(!err, 'There is no error when adding a message.') 17 | t.ok(id, 'There is an id returned when adding a message.') 18 | next() 19 | }) 20 | }, 21 | function(next) { 22 | // get something now and it shouldn't be there 23 | queue.get(function(err, msg) { 24 | t.ok(!err, 'No error when getting no messages') 25 | t.ok(!msg, 'No msg received') 26 | // now wait 4s 27 | setTimeout(next, 4 * 1000) 28 | }) 29 | }, 30 | function(next) { 31 | // get something now and it SHOULD be there 32 | queue.get(function(err, msg) { 33 | t.ok(!err, 'No error when getting a message') 34 | t.ok(msg.id, 'Got a message id now that the message delay has passed') 35 | queue.ack(msg.ack, next) 36 | }) 37 | }, 38 | function(next) { 39 | queue.get(function(err, msg) { 40 | // no more messages 41 | t.ok(!err, 'No error when getting no messages') 42 | t.ok(!msg, 'No more messages') 43 | next() 44 | }) 45 | }, 46 | ], 47 | function(err) { 48 | if (err) t.fail(err) 49 | t.pass('Finished test ok') 50 | t.end() 51 | } 52 | ) 53 | }) 54 | 55 | test('delay: check an individual message delay overrides the queue delay', function(t) { 56 | var queue = mongoDbQueue(db, 'delay') 57 | 58 | async.series( 59 | [ 60 | function(next) { 61 | queue.add('I am delayed by 3 seconds', { delay : 3 }, function(err, id) { 62 | t.ok(!err, 'There is no error when adding a message.') 63 | t.ok(id, 'There is an id returned when adding a message.') 64 | next() 65 | }) 66 | }, 67 | function(next) { 68 | // get something now and it shouldn't be there 69 | queue.get(function(err, msg) { 70 | t.ok(!err, 'No error when getting no messages') 71 | t.ok(!msg, 'No msg received') 72 | // now wait 4s 73 | setTimeout(next, 4 * 1000) 74 | }) 75 | }, 76 | function(next) { 77 | // get something now and it SHOULD be there 78 | queue.get(function(err, msg) { 79 | t.ok(!err, 'No error when getting a message') 80 | t.ok(msg.id, 'Got a message id now that the message delay has passed') 81 | queue.ack(msg.ack, next) 82 | }) 83 | }, 84 | function(next) { 85 | queue.get(function(err, msg) { 86 | // no more messages 87 | t.ok(!err, 'No error when getting no messages') 88 | t.ok(!msg, 'No more messages') 89 | next() 90 | }) 91 | }, 92 | ], 93 | function(err) { 94 | if (err) t.fail(err) 95 | t.pass('Finished test ok') 96 | t.end() 97 | } 98 | ) 99 | }) 100 | 101 | test('client.close()', function(t) { 102 | t.pass('client.close()') 103 | client.close() 104 | t.end() 105 | }) 106 | 107 | }) 108 | -------------------------------------------------------------------------------- /test/indexes.js: -------------------------------------------------------------------------------- 1 | var async = require('async') 2 | var test = require('tape') 3 | 4 | var setup = require('./setup.js') 5 | var mongoDbQueue = require('../') 6 | 7 | setup(function(client, db) { 8 | 9 | test('visibility: check message is back in queue after 3s', function(t) { 10 | t.plan(2) 11 | 12 | var queue = mongoDbQueue(db, 'visibility', { visibility : 3 }) 13 | 14 | queue.createIndexes(function(err, indexName) { 15 | t.ok(!err, 'There was no error when running .ensureIndexes()') 16 | t.ok(indexName, 'receive indexName we created') 17 | t.end() 18 | }) 19 | }) 20 | 21 | test('client.close()', function(t) { 22 | t.pass('client.close()') 23 | client.close() 24 | t.end() 25 | }) 26 | 27 | }) 28 | -------------------------------------------------------------------------------- /test/many.js: -------------------------------------------------------------------------------- 1 | var async = require('async') 2 | var test = require('tape') 3 | 4 | var setup = require('./setup.js') 5 | var mongoDbQueue = require('../') 6 | 7 | var total = 250 8 | 9 | setup(function(client, db) { 10 | 11 | test('many: add ' + total + ' messages, get ' + total + ' back', function(t) { 12 | var queue = mongoDbQueue(db, 'many') 13 | var msgs = [] 14 | var msgsToQueue = [] 15 | 16 | async.series( 17 | [ 18 | function(next) { 19 | var i 20 | for(i=0; i { 25 | // we can throw since this is test-only 26 | if (err) throw err 27 | 28 | const db = client.db(dbName) 29 | 30 | // empty out some collections to make sure there are no messages 31 | let done = 0 32 | collections.forEach((col) => { 33 | db.collection(col).deleteMany(() => { 34 | done += 1 35 | if ( done === collections.length ) { 36 | callback(client, db) 37 | } 38 | }) 39 | }) 40 | }) 41 | 42 | } 43 | -------------------------------------------------------------------------------- /test/stats.js: -------------------------------------------------------------------------------- 1 | var async = require('async') 2 | var test = require('tape') 3 | 4 | var setup = require('./setup.js') 5 | var mongoDbQueue = require('../') 6 | 7 | setup(function(client, db) { 8 | 9 | test('first test', function(t) { 10 | var queue = mongoDbQueue(db, 'stats') 11 | t.ok(queue, 'Queue created ok') 12 | t.end() 13 | }); 14 | 15 | test('stats for a single message added, received and acked', function(t) { 16 | var queue = mongoDbQueue(db, 'stats1') 17 | var msg 18 | 19 | async.series( 20 | [ 21 | function(next) { 22 | queue.add('Hello, World!', function(err, id) { 23 | t.ok(!err, 'There is no error when adding a message.') 24 | t.ok(id, 'Received an id for this message') 25 | next() 26 | }) 27 | }, 28 | function(next) { 29 | queue.total(function(err, count) { 30 | t.equal(count, 1, 'Total number of messages is one') 31 | next() 32 | }) 33 | }, 34 | function(next) { 35 | queue.size(function(err, count) { 36 | t.equal(count, 1, 'Size of queue is one') 37 | next() 38 | }) 39 | }, 40 | function(next) { 41 | queue.inFlight(function(err, count) { 42 | t.equal(count, 0, 'There are no inFlight messages') 43 | next() 44 | }) 45 | }, 46 | function(next) { 47 | queue.done(function(err, count) { 48 | t.equal(count, 0, 'There are no done messages') 49 | next() 50 | }) 51 | }, 52 | function(next) { 53 | // let's set one to be inFlight 54 | queue.get(function(err, newMsg) { 55 | msg = newMsg 56 | next() 57 | }) 58 | }, 59 | function(next) { 60 | queue.total(function(err, count) { 61 | t.equal(count, 1, 'Total number of messages is still one') 62 | next() 63 | }) 64 | }, 65 | function(next) { 66 | queue.size(function(err, count) { 67 | t.equal(count, 0, 'Size of queue is now zero (ie. none to come)') 68 | next() 69 | }) 70 | }, 71 | function(next) { 72 | queue.inFlight(function(err, count) { 73 | t.equal(count, 1, 'There is one inflight message') 74 | next() 75 | }) 76 | }, 77 | function(next) { 78 | queue.done(function(err, count) { 79 | t.equal(count, 0, 'There are still no done messages') 80 | next() 81 | }) 82 | }, 83 | function(next) { 84 | // now ack that message 85 | queue.ack(msg.ack, function(err, newMsg) { 86 | msg = newMsg 87 | next() 88 | }) 89 | }, 90 | function(next) { 91 | queue.total(function(err, count) { 92 | t.equal(count, 1, 'Total number of messages is again one') 93 | next() 94 | }) 95 | }, 96 | function(next) { 97 | queue.size(function(err, count) { 98 | t.equal(count, 0, 'Size of queue is still zero (ie. none to come)') 99 | next() 100 | }) 101 | }, 102 | function(next) { 103 | queue.inFlight(function(err, count) { 104 | t.equal(count, 0, 'There are no inflight messages anymore') 105 | next() 106 | }) 107 | }, 108 | function(next) { 109 | queue.done(function(err, count) { 110 | t.equal(count, 1, 'There is now one processed message') 111 | next() 112 | }) 113 | }, 114 | ], 115 | function(err) { 116 | t.ok(!err, 'No error when doing stats on one message') 117 | t.end() 118 | } 119 | ) 120 | }) 121 | 122 | 123 | // ToDo: add more tests for adding a message, getting it and letting it lapse 124 | // then re-checking all stats. 125 | 126 | test('stats for a single message added, received, timed-out and back on queue', function(t) { 127 | var queue = mongoDbQueue(db, 'stats2', { visibility : 3 }) 128 | 129 | async.series( 130 | [ 131 | function(next) { 132 | queue.add('Hello, World!', function(err, id) { 133 | t.ok(!err, 'There is no error when adding a message.') 134 | t.ok(id, 'Received an id for this message') 135 | next() 136 | }) 137 | }, 138 | function(next) { 139 | queue.total(function(err, count) { 140 | t.equal(count, 1, 'Total number of messages is one') 141 | next() 142 | }) 143 | }, 144 | function(next) { 145 | queue.size(function(err, count) { 146 | t.equal(count, 1, 'Size of queue is one') 147 | next() 148 | }) 149 | }, 150 | function(next) { 151 | queue.inFlight(function(err, count) { 152 | t.equal(count, 0, 'There are no inFlight messages') 153 | next() 154 | }) 155 | }, 156 | function(next) { 157 | queue.done(function(err, count) { 158 | t.equal(count, 0, 'There are no done messages') 159 | next() 160 | }) 161 | }, 162 | function(next) { 163 | // let's set one to be inFlight 164 | queue.get(function(err, msg) { 165 | // msg is ignored, we don't care about the message here 166 | setTimeout(next, 4 * 1000) 167 | }) 168 | }, 169 | function(next) { 170 | queue.total(function(err, count) { 171 | t.equal(count, 1, 'Total number of messages is still one') 172 | next() 173 | }) 174 | }, 175 | function(next) { 176 | queue.size(function(err, count) { 177 | t.equal(count, 1, 'Size of queue is still at one') 178 | next() 179 | }) 180 | }, 181 | function(next) { 182 | queue.inFlight(function(err, count) { 183 | t.equal(count, 0, 'There are no inflight messages again') 184 | next() 185 | }) 186 | }, 187 | function(next) { 188 | queue.done(function(err, count) { 189 | t.equal(count, 0, 'There are still no done messages') 190 | next() 191 | }) 192 | }, 193 | ], 194 | function(err) { 195 | t.ok(!err, 'No error when doing stats on one message') 196 | t.end() 197 | } 198 | ) 199 | }) 200 | 201 | test('client.close()', function(t) { 202 | t.pass('client.close()') 203 | client.close() 204 | t.end() 205 | }) 206 | 207 | }) 208 | 209 | -------------------------------------------------------------------------------- /test/visibility.js: -------------------------------------------------------------------------------- 1 | var async = require('async') 2 | var test = require('tape') 3 | 4 | var setup = require('./setup.js') 5 | var mongoDbQueue = require('../') 6 | 7 | setup(function(client, db) { 8 | 9 | test('visibility: check message is back in queue after 3s', function(t) { 10 | var queue = mongoDbQueue(db, 'visibility', { visibility : 3 }) 11 | 12 | async.series( 13 | [ 14 | function(next) { 15 | queue.add('Hello, World!', function(err) { 16 | t.ok(!err, 'There is no error when adding a message.') 17 | next() 18 | }) 19 | }, 20 | function(next) { 21 | queue.get(function(err, msg) { 22 | // wait over 3s so the msg returns to the queue 23 | t.ok(msg.id, 'Got a msg.id (sanity check)') 24 | setTimeout(next, 4 * 1000) 25 | }) 26 | }, 27 | function(next) { 28 | queue.get(function(err, msg) { 29 | // yes, there should be a message on the queue again 30 | t.ok(msg.id, 'Got a msg.id (sanity check)') 31 | queue.ack(msg.ack, function(err) { 32 | t.ok(!err, 'No error when acking the message') 33 | next() 34 | }) 35 | }) 36 | }, 37 | function(next) { 38 | queue.get(function(err, msg) { 39 | // no more messages 40 | t.ok(!err, 'No error when getting no messages') 41 | t.ok(!msg, 'No msg received') 42 | next() 43 | }) 44 | }, 45 | ], 46 | function(err) { 47 | if (err) t.fail(err) 48 | t.pass('Finished test ok') 49 | t.end() 50 | } 51 | ) 52 | }) 53 | 54 | test("visibility: check that a late ack doesn't remove the msg", function(t) { 55 | var queue = mongoDbQueue(db, 'visibility', { visibility : 3 }) 56 | var originalAck 57 | 58 | async.series( 59 | [ 60 | function(next) { 61 | queue.add('Hello, World!', function(err) { 62 | t.ok(!err, 'There is no error when adding a message.') 63 | next() 64 | }) 65 | }, 66 | function(next) { 67 | queue.get(function(err, msg) { 68 | t.ok(msg.id, 'Got a msg.id (sanity check)') 69 | 70 | // remember this original ack 71 | originalAck = msg.ack 72 | 73 | // wait over 3s so the msg returns to the queue 74 | setTimeout(function() { 75 | t.pass('Back from timeout, now acking the message') 76 | 77 | // now ack the message but too late - it shouldn't be deleted 78 | queue.ack(msg.ack, function(err, msg) { 79 | t.ok(err, 'Got an error when acking the message late') 80 | t.ok(!msg, 'No message was updated') 81 | next() 82 | }) 83 | }, 4 * 1000) 84 | }) 85 | }, 86 | function(next) { 87 | queue.get(function(err, msg) { 88 | // the message should now be able to be retrieved, with a new 'ack' id 89 | t.ok(msg.id, 'Got a msg.id (sanity check)') 90 | t.notEqual(msg.ack, originalAck, 'Original ack and new ack are different') 91 | 92 | // now ack this new retrieval 93 | queue.ack(msg.ack, next) 94 | }) 95 | }, 96 | function(next) { 97 | queue.get(function(err, msg) { 98 | // no more messages 99 | t.ok(!err, 'No error when getting no messages') 100 | t.ok(!msg, 'No msg received') 101 | next() 102 | }) 103 | }, 104 | ], 105 | function(err) { 106 | if (err) t.fail(err) 107 | t.pass('Finished test ok') 108 | t.end() 109 | } 110 | ) 111 | }) 112 | 113 | test("visibility: check visibility option overrides the queue visibility", function(t) { 114 | var queue = mongoDbQueue(db, 'visibility', { visibility : 2 }) 115 | var originalAck 116 | 117 | async.series( 118 | [ 119 | function(next) { 120 | queue.add('Hello, World!', function(err) { 121 | t.ok(!err, 'There is no error when adding a message.') 122 | next() 123 | }) 124 | }, 125 | function(next) { 126 | queue.get({ visibility: 4 }, function(err, msg) { 127 | // wait over 2s so the msg would normally have returns to the queue 128 | t.ok(msg.id, 'Got a msg.id (sanity check)') 129 | setTimeout(next, 3 * 1000) 130 | }) 131 | }, 132 | function(next) { 133 | queue.get(function(err, msg) { 134 | // messages should not be back yet 135 | t.ok(!err, 'No error when getting no messages') 136 | t.ok(!msg, 'No msg received') 137 | // wait 2s so the msg should have returns to the queue 138 | setTimeout(next, 2 * 1000) 139 | }) 140 | }, 141 | function(next) { 142 | queue.get(function(err, msg) { 143 | // yes, there should be a message on the queue again 144 | t.ok(msg.id, 'Got a msg.id (sanity check)') 145 | queue.ack(msg.ack, function(err) { 146 | t.ok(!err, 'No error when acking the message') 147 | next() 148 | }) 149 | }) 150 | }, 151 | function(next) { 152 | queue.get(function(err, msg) { 153 | // no more messages 154 | t.ok(!err, 'No error when getting no messages') 155 | t.ok(!msg, 'No msg received') 156 | next() 157 | }) 158 | } 159 | ], 160 | function(err) { 161 | if (err) t.fail(err) 162 | t.pass('Finished test ok') 163 | t.end() 164 | } 165 | ) 166 | }) 167 | 168 | test('client.close()', function(t) { 169 | t.pass('client.close()') 170 | client.close() 171 | t.end() 172 | }) 173 | 174 | }) 175 | --------------------------------------------------------------------------------