diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..47beb92 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,8 @@ +*.json +*.sh +*.yml +*.md +*.d.ts +*.js.map +*.js +!test/*.js diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 0000000..f1108bb --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,16 @@ +extends: + - 'plugin:@reedsy/recommended' +parserOptions: + project: './tsconfig.json' +env: + node: true +rules: + # default exports have bad CJS/ESM interoperability + no-restricted-exports: + - error + - restrictDefaultExports: + direct: true + named: true + defaultFrom: true + namedFrom: true + namespaceFrom: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..11fe184 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,61 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + mongodb: + - '6.0' + mongo_driver: + - mongodb6 + services: + mongodb: + image: mongo:${{ matrix.mongodb }} + ports: + - 27017:27017 + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20.x' + registry-url: 'https://npm.pkg.github.com' + - name: Install + # Skip post-install to avoid malicious scripts stealing PAT + run: npm install --ignore-script + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Post-install + run: npm rebuild && npm run prepare --if-present + - name: Lint + run: npm run lint + - name: Test + run: npm test + env: + MONGO_DRIVER: ${{ matrix.mongo_driver }} + - name: Release + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + run: ./release.sh + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # A silly job just so we can mark this as the required check for PRs + # instead of maintaining the full list of matrix jobs + success: + needs: + - build + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Success + run: echo "Success" diff --git a/.gitignore b/.gitignore index 04064b0..7540984 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ node_modules/* *.log *~ +package-lock.json +*.d.ts +*.js.map +*.js +!test/*.js diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..a09524a --- /dev/null +++ b/.npmignore @@ -0,0 +1,3 @@ +**/* +!*.d.ts +!*.js.map diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..f06c805 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@reedsy:registry=https://npm.pkg.github.com diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6342d3b..0000000 --- a/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ -language: node_js -node_js: - - "8" - - "10" - - "11" -services: mongodb diff --git a/README.md b/README.md index 57a7206..5684d76 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ const client = new mongodb.MongoClient(url, { useNewUrlParser: true }) client.connect(err => { const db = client.db('test') - const queue = mongoDbQueue(db, 'my-queue') + const queue = new MongoDbQueue(db, 'my-queue') // ... @@ -102,9 +102,9 @@ passed in: var mongoDbQueue = require('mongodb-queue') // an instance of a queue -var queue1 = mongoDbQueue(db, 'a-queue') +var queue1 = new MongoDbQueue(db, 'a-queue') // another queue which uses the same collection as above -var queue2 = mongoDbQueue(db, 'a-queue') +var queue2 = new MongoDbQueue(db, 'a-queue') ``` Using `queue1` and `queue2` here won't interfere with each other and will play along nicely, but that's not @@ -116,7 +116,7 @@ it's not something you should do. To pass in options for the queue: ``` -var resizeQueue = mongoDbQueue(db, 'resize-queue', { visibility : 30, delay : 15 }) +var resizeQueue = new MongoDbQueue(db, 'resize-queue', { visibility : 30, delay : 15 }) ``` This example shows a queue with a message visibility of 30s and a delay to each message of 15s. @@ -131,8 +131,8 @@ Each queue you create will be it's own collection. e.g. ``` -var resizeImageQueue = mongoDbQueue(db, 'resize-image-queue') -var notifyOwnerQueue = mongoDbQueue(db, 'notify-owner-queue') +var resizeImageQueue = new MongoDbQueue(db, 'resize-image-queue') +var notifyOwnerQueue = new MongoDbQueue(db, 'notify-owner-queue') ``` This will create two collections in MongoDB called `resize-image-queue` and `notify-owner-queue`. @@ -149,7 +149,7 @@ You may set this visibility window on a per queue basis. For example, to set the visibility to 15 seconds: ``` -var queue = mongoDbQueue(db, 'queue', { visibility : 15 }) +var queue = new MongoDbQueue(db, 'queue', { visibility : 15 }) ``` All messages in this queue now have a visibility window of 15s, instead of the @@ -167,7 +167,7 @@ retrieval 10s after being added. To delay all messages by 10 seconds, try this: ``` -var queue = mongoDbQueue(db, 'queue', { delay : 10 }) +var queue = new MongoDbQueue(db, 'queue', { delay : 10 }) ``` This is now the default for every message added to the queue. @@ -182,8 +182,8 @@ automatically see problem messages. Pass in a queue (that you created) onto which these messages will be pushed: ```js -var deadQueue = mongoDbQueue(db, 'dead-queue') -var queue = mongoDbQueue(db, 'queue', { deadQueue : deadQueue }) +var deadQueue = new MongoDbQueue(db, 'dead-queue') +var queue = new MongoDbQueue(db, 'queue', { deadQueue : deadQueue }) ``` If you pop a message off the `queue` over `maxRetries` times and still have not acked it, @@ -237,6 +237,18 @@ msg = { Notice that the payload from the `deadQueue` is exactly the same as the original message when it was on the original queue (except with the number of tries set to 5). +### returnDocument ### + +The `mongodb` Node.js driver [deprecated](https://github.com/mongodb/node-mongodb-native/pull/2808) +use of `returnOriginal` in favor of `returnDocument` when using `findOneAndUpdate()`. + +If you want to opt in to using the newer `returnDocument`, set the `returnDocument` option +to `true`: + +``` +var queue = new MongoDbQueue(db, 'queue', { returnDocument : true }) +``` + ## Operations ## ### .add() ### @@ -345,6 +357,17 @@ queue.get((err, msg) => { }) ``` +You can also reset the job tries, effectively creating an atomic ack + add for the +same job using `resetTries`: + +```js +queue.get((err, msg) => { + queue.ping(msg.ack, { resetTries: true }, (err, id) => { + // This message now has 0 tries + }) +}) +``` + ### .total() ### Returns the total number of messages that has ever been in the queue, including diff --git a/mongodb-queue.js b/mongodb-queue.js deleted file mode 100644 index a712f1c..0000000 --- a/mongodb-queue.js +++ /dev/null @@ -1,268 +0,0 @@ -/** - * - * mongodb-queue.js - Use your existing MongoDB as a local queue. - * - * Copyright (c) 2014 Andrew Chilton - * - http://chilts.org/ - * - andychilton@gmail.com - * - * License: http://chilts.mit-license.org/2014/ - * - **/ - -var crypto = require('crypto') - -// some helper functions -function id() { - return crypto.randomBytes(16).toString('hex') -} - -function now() { - return (new Date()).toISOString() -} - -function nowPlusSecs(secs) { - return (new Date(Date.now() + secs * 1000)).toISOString() -} - -module.exports = function(db, name, opts) { - return new Queue(db, name, opts) -} - -// the Queue object itself -function Queue(db, name, opts) { - if ( !db ) { - throw new Error("mongodb-queue: provide a mongodb.MongoClient.db") - } - if ( !name ) { - throw new Error("mongodb-queue: provide a queue name") - } - opts = opts || {} - - this.db = db - this.name = name - this.col = db.collection(name) - this.visibility = opts.visibility || 30 - this.delay = opts.delay || 0 - - if ( opts.deadQueue ) { - this.deadQueue = opts.deadQueue - this.maxRetries = opts.maxRetries || 5 - } -} - -Queue.prototype.createIndexes = function(callback) { - var self = this - - self.col.createIndex({ deleted : 1, visible : 1 }, function(err, indexname) { - if (err) return callback(err) - self.col.createIndex({ ack : 1 }, { unique : true, sparse : true }, function(err) { - if (err) return callback(err) - callback(null, indexname) - }) - }) -} - -Queue.prototype.add = function(payload, opts, callback) { - var self = this - if ( !callback ) { - callback = opts - opts = {} - } - var delay = opts.delay || self.delay - var visible = delay ? nowPlusSecs(delay) : now() - - var msgs = [] - if (payload instanceof Array) { - if (payload.length === 0) { - var errMsg = 'Queue.add(): Array payload length must be greater than 0' - return callback(new Error(errMsg)) - } - payload.forEach(function(payload) { - msgs.push({ - visible : visible, - payload : payload, - }) - }) - } else { - msgs.push({ - visible : visible, - payload : payload, - }) - } - - self.col.insertMany(msgs, function(err, results) { - if (err) return callback(err) - if (payload instanceof Array) return callback(null, '' + results.insertedIds) - callback(null, '' + results.ops[0]._id) - }) -} - -Queue.prototype.get = function(opts, callback) { - var self = this - if ( !callback ) { - callback = opts - opts = {} - } - - var visibility = opts.visibility || self.visibility - var query = { - deleted : null, - visible : { $lte : now() }, - } - var sort = { - _id : 1 - } - var update = { - $inc : { tries : 1 }, - $set : { - ack : id(), - visible : nowPlusSecs(visibility), - } - } - - self.col.findOneAndUpdate(query, update, { sort: sort, returnOriginal : false }, function(err, result) { - if (err) return callback(err) - var msg = result.value - if (!msg) return callback() - - // convert to an external representation - msg = { - // convert '_id' to an 'id' string - id : '' + msg._id, - ack : msg.ack, - payload : msg.payload, - tries : msg.tries, - } - // if we have a deadQueue, then check the tries, else don't - if ( self.deadQueue ) { - // check the tries - if ( msg.tries > self.maxRetries ) { - // So: - // 1) add this message to the deadQueue - // 2) ack this message from the regular queue - // 3) call ourself to return a new message (if exists) - self.deadQueue.add(msg, function(err) { - if (err) return callback(err) - self.ack(msg.ack, function(err) { - if (err) return callback(err) - self.get(callback) - }) - }) - return - } - } - - callback(null, msg) - }) -} - -Queue.prototype.ping = function(ack, opts, callback) { - var self = this - if ( !callback ) { - callback = opts - opts = {} - } - - var visibility = opts.visibility || self.visibility - var query = { - ack : ack, - visible : { $gt : now() }, - deleted : null, - } - var update = { - $set : { - visible : nowPlusSecs(visibility) - } - } - self.col.findOneAndUpdate(query, update, { returnOriginal : false }, function(err, msg, blah) { - if (err) return callback(err) - if ( !msg.value ) { - return callback(new Error("Queue.ping(): Unidentified ack : " + ack)) - } - callback(null, '' + msg.value._id) - }) -} - -Queue.prototype.ack = function(ack, callback) { - var self = this - - var query = { - ack : ack, - visible : { $gt : now() }, - deleted : null, - } - var update = { - $set : { - deleted : now(), - } - } - self.col.findOneAndUpdate(query, update, { returnOriginal : false }, function(err, msg, blah) { - if (err) return callback(err) - if ( !msg.value ) { - return callback(new Error("Queue.ack(): Unidentified ack : " + ack)) - } - callback(null, '' + msg.value._id) - }) -} - -Queue.prototype.clean = function(callback) { - var self = this - - var query = { - deleted : { $exists : true }, - } - - self.col.deleteMany(query, callback) -} - -Queue.prototype.total = function(callback) { - var self = this - - self.col.countDocuments(function(err, count) { - if (err) return callback(err) - callback(null, count) - }) -} - -Queue.prototype.size = function(callback) { - var self = this - - var query = { - deleted : null, - visible : { $lte : now() }, - } - - self.col.countDocuments(query, function(err, count) { - if (err) return callback(err) - callback(null, count) - }) -} - -Queue.prototype.inFlight = function(callback) { - var self = this - - var query = { - ack : { $exists : true }, - visible : { $gt : now() }, - deleted : null, - } - - self.col.countDocuments(query, function(err, count) { - if (err) return callback(err) - callback(null, count) - }) -} - -Queue.prototype.done = function(callback) { - var self = this - - var query = { - deleted : { $exists : true }, - } - - self.col.countDocuments(query, function(err, count) { - if (err) return callback(err) - callback(null, count) - }) -} diff --git a/mongodb-queue.ts b/mongodb-queue.ts new file mode 100644 index 0000000..8cbd6f0 --- /dev/null +++ b/mongodb-queue.ts @@ -0,0 +1,277 @@ +/** + * + * mongodb-queue.js - Use your existing MongoDB as a local queue. + * + * Copyright (c) 2014 Andrew Chilton + * - http://chilts.org/ + * - andychilton@gmail.com + * + * License: http://chilts.mit-license.org/2014/ + * + **/ + +import {Collection, CreateIndexesOptions, Db, Filter, FindOneAndUpdateOptions, ObjectId, Sort, UpdateFilter, WithId} from 'mongodb'; + +function now(): string { + return (new Date()).toISOString(); +} + +function nowPlusSecs(secs: number): string { + return (new Date(Date.now() + secs * 1000)).toISOString(); +} + +export type QueueOptions = { + visibility?: number; + delay?: number; + deadQueue?: MongoDBQueue; + maxRetries?: number; + expireAfterSeconds?: number; +}; + +export type AddOptions = { + delay?: number; +}; + +export type GetOptions = { + visibility?: number; +}; + +export type PingOptions = { + visibility?: number; + resetTries?: boolean; + resetAck?: boolean; +}; + +export type BaseMessage = { + payload: T; + visible: string; +}; + +export type Message = BaseMessage & { + ack: string; + tries: number; + deleted?: Date; +}; + +export type ExternalMessage = { + id: string; + ack: string; + payload: T; + tries: number; +}; + +export class MongoDBQueue { + private readonly col: Collection>>; + private readonly visibility: number; + private readonly delay: number; + private readonly maxRetries: number; + private readonly deadQueue: MongoDBQueue; + private readonly expireAfterSeconds: number; + + public constructor(db: Db, name: string, opts: QueueOptions = {}) { + if (!db) { + throw new Error('mongodb-queue: provide a mongodb.MongoClient.db'); + } + if (!name) { + throw new Error('mongodb-queue: provide a queue name'); + } + + this.col = db.collection(name); + this.visibility = opts.visibility || 30; + this.delay = opts.delay || 0; + this.expireAfterSeconds = opts.expireAfterSeconds; + + if (opts.deadQueue) { + this.deadQueue = opts.deadQueue; + this.maxRetries = opts.maxRetries || 5; + } + } + + public async createIndexes(): Promise { + const deletedOptions: CreateIndexesOptions = {sparse: true}; + if (typeof this.expireAfterSeconds === 'number') { + deletedOptions.expireAfterSeconds = this.expireAfterSeconds; + } + + await Promise.all([ + this.col.createIndex({visible: 1}, {sparse: true}), + this.col.createIndex({ack: 1}, {unique: true, sparse: true}), + this.col.createIndex({deleted: 1}, deletedOptions), + + // Index for efficient counts on in-flight + this.col.createIndex({visible: 1, ack: 1}, { + partialFilterExpression: { + visible: {$exists: true}, + ack: {$exists: true}, + }, + }), + ]); + } + + public async add(payload: T | T[], opts: AddOptions = {}): Promise { + const delay = opts.delay || this.delay; + const visible = delay ? nowPlusSecs(delay) : now(); + + const msgs: BaseMessage[] = []; + if (payload instanceof Array) { + if (payload.length === 0) { + throw new Error('Queue.add(): Array payload length must be greater than 0'); + } + payload.forEach(function(payload) { + msgs.push({ + visible: visible, + payload: payload, + }); + }); + } else { + msgs.push({ + visible: visible, + payload: payload, + }); + } + + const results = await this.col.insertMany(msgs, {ignoreUndefined: true}); + if (payload instanceof Array) return '' + results.insertedIds; + return '' + results.insertedIds[0]; + } + + public async get(opts: GetOptions = {}): Promise | null> { + const visibility = opts.visibility || this.visibility; + const query: Filter>> = { + visible: {$lte: now()}, + }; + const sort: Sort = { + visible: 1, + }; + const update: UpdateFilter> = { + $inc: {tries: 1}, + $set: { + ack: new ObjectId().toHexString(), + visible: nowPlusSecs(visibility), + }, + }; + const options = { + sort: sort, + returnDocument: 'after', + includeResultMetadata: true, + } satisfies FindOneAndUpdateOptions; + + const result = await this.col.findOneAndUpdate(query, update, options); + const msg = result.value as WithId>; + if (!msg) return null; + + // convert to an external representation + const externalMessage: ExternalMessage = { + // convert '_id' to an 'id' string + id: '' + msg._id, + ack: msg.ack, + payload: msg.payload, + tries: msg.tries, + }; + + // check the tries + if (this.deadQueue && msg.tries > this.maxRetries) { + // So: + // 1) add this message to the deadQueue + // 2) ack this message from the regular queue + // 3) call ourself to return a new message (if exists) + await this.deadQueue.add(externalMessage); + await this.ack(msg.ack); + return this.get(); + } + + return externalMessage; + } + + public async ping(ack: string, opts: PingOptions = {}): Promise { + const visibility = opts.visibility || this.visibility; + const query: Filter>> = { + ack: ack, + visible: {$gt: now()}, + }; + const update: UpdateFilter> = { + $set: { + visible: nowPlusSecs(visibility), + }, + }; + const options = { + returnDocument: 'after', + includeResultMetadata: true, + } satisfies FindOneAndUpdateOptions; + + if (opts.resetTries) { + update.$set = { + ...update.$set, + tries: 0, + }; + } + + if (opts.resetAck) { + update.$unset = {ack: 1}; + } + + const msg = await this.col.findOneAndUpdate(query, update, options); + if (!msg.value) { + throw new Error('Queue.ping(): Unidentified ack : ' + ack); + } + return '' + msg.value._id; + } + + public async ack(ack: string): Promise { + const query: Filter>> = { + ack: ack, + visible: {$gt: now()}, + }; + const update: UpdateFilter> = { + $set: { + deleted: new Date(), + }, + $unset: { + visible: 1, + }, + }; + const options = { + returnDocument: 'after', + includeResultMetadata: true, + } satisfies FindOneAndUpdateOptions; + const msg = await this.col.findOneAndUpdate(query, update, options); + if (!msg.value) { + throw new Error('Queue.ack(): Unidentified ack : ' + ack); + } + return '' + msg.value._id; + } + + public async clean(): Promise { + const query = { + deleted: {$exists: true}, + }; + + await this.col.deleteMany(query); + } + + public async total(): Promise { + return this.col.countDocuments(); + } + + public async size(): Promise { + return this.col.countDocuments({ + visible: {$lte: now()}, + }); + } + + public async inFlight(): Promise { + return this.col.countDocuments({ + // For some unknown reason, MongoDB refuses to use the partial index with + // {$exists: true}, but *will* use it if we use {$gt: ''} + // https://www.mongodb.com/community/forums/t/partial-index-is-not-used-during-search/290507/2 + ack: {$gt: ''}, + visible: {$gt: now()}, + }); + } + + public async done(): Promise { + return this.col.countDocuments({ + deleted: {$exists: true}, + }); + } +} diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 7a5a278..0000000 --- a/package-lock.json +++ /dev/null @@ -1,380 +0,0 @@ -{ - "name": "mongodb-queue", - "version": "4.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", - "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", - "dev": true, - "requires": { - "lodash": "^4.17.11" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "bson": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.0.tgz", - "integrity": "sha512-9Aeai9TacfNtWXOYarkFJRW2CWo+dRon+fuLZYJmvLV3+MiUp0bEI6IAZfXEIg7/Pl/7IWlLaDnhzTsD81etQA==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "deep-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", - "dev": true - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", - "dev": true - }, - "es-abstract": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", - "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.0", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "is-callable": "^1.1.4", - "is-regex": "^1.0.4", - "object-keys": "^1.0.12" - } - }, - "es-to-primitive": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", - "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "requires": { - "is-callable": "^1.1.3" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "is-callable": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", - "dev": true - }, - "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true - }, - "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, - "requires": { - "has": "^1.0.1" - } - }, - "is-symbol": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", - "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", - "dev": true, - "requires": { - "has-symbols": "^1.0.0" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "mongodb": { - "version": "3.1.13", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.1.13.tgz", - "integrity": "sha512-sz2dhvBZQWf3LRNDhbd30KHVzdjZx9IKC0L+kSZ/gzYquCF5zPOgGqRz6sSCqYZtKP2ekB4nfLxhGtzGHnIKxA==", - "dev": true, - "requires": { - "mongodb-core": "3.1.11", - "safe-buffer": "^5.1.2" - } - }, - "mongodb-core": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.1.11.tgz", - "integrity": "sha512-rD2US2s5qk/ckbiiGFHeu+yKYDXdJ1G87F6CG3YdaZpzdOm5zpoAZd/EKbPmFO6cQZ+XVXBXBJ660sSI0gc6qg==", - "dev": true, - "requires": { - "bson": "^1.1.0", - "require_optional": "^1.0.1", - "safe-buffer": "^5.1.2", - "saslprep": "^1.0.0" - } - }, - "object-inspect": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", - "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", - "dev": true - }, - "object-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz", - "integrity": "sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "require_optional": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", - "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", - "dev": true, - "requires": { - "resolve-from": "^2.0.0", - "semver": "^5.1.0" - } - }, - "resolve": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", - "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", - "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=", - "dev": true - }, - "resumer": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", - "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", - "dev": true, - "requires": { - "through": "~2.3.4" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "saslprep": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.2.tgz", - "integrity": "sha512-4cDsYuAjXssUSjxHKRe4DTZC0agDwsCqcMqtJAQPzC74nJ7LfAJflAtC1Zed5hMzEQKj82d3tuzqdGNRsLJ4Gw==", - "dev": true, - "optional": true, - "requires": { - "sparse-bitfield": "^3.0.3" - } - }, - "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", - "dev": true - }, - "sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", - "dev": true, - "optional": true, - "requires": { - "memory-pager": "^1.0.2" - } - }, - "string.prototype.trim": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz", - "integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.0", - "function-bind": "^1.0.2" - } - }, - "tape": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/tape/-/tape-4.10.1.tgz", - "integrity": "sha512-G0DywYV1jQeY3axeYnXUOt6ktnxS9OPJh97FGR3nrua8lhWi1zPflLxcAHavZ7Jf3qUfY7cxcVIVFa4mY2IY1w==", - "dev": true, - "requires": { - "deep-equal": "~1.0.1", - "defined": "~1.0.0", - "for-each": "~0.3.3", - "function-bind": "~1.1.1", - "glob": "~7.1.3", - "has": "~1.0.3", - "inherits": "~2.0.3", - "minimist": "~1.2.0", - "object-inspect": "~1.6.0", - "resolve": "~1.10.0", - "resumer": "~0.0.0", - "string.prototype.trim": "~1.1.2", - "through": "~2.3.8" - } - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - } - } -} diff --git a/package.json b/package.json index d2b77e9..adf7ba0 100644 --- a/package.json +++ b/package.json @@ -1,21 +1,31 @@ { - "name": "mongodb-queue", - "version": "4.0.0", + "name": "@reedsy/mongodb-queue", + "version": "8.2.0", "description": "Message queues which uses MongoDB.", "main": "mongodb-queue.js", "scripts": { + "build": "tsc --pretty", + "lint": "eslint '**/*' --max-warnings 0", + "prepare": "npm run build", + "pretest": "npm run build", "test": "set -e; for FILE in test/*.js; do echo --- $FILE ---; node $FILE; done" }, - "dependencies": {}, "devDependencies": { - "async": "^2.6.2", - "mongodb": "^3.1.13", - "tape": "^4.10.1" + "@reedsy/eslint-plugin": "^0.14.2", + "eslint": "^8.35.0", + "mongodb4": "npm:mongodb@^4.0.0", + "mongodb5": "npm:mongodb@^5.0.0", + "mongodb6": "npm:mongodb@^6.0.0", + "tape": "^4.10.1", + "typescript": "^4.9.5" + }, + "peerDependencies": { + "mongodb": "^4.0.0 || ^5.0.0 || ^6.0.0" }, "homepage": "https://github.com/chilts/mongodb-queue", "repository": { "type": "git", - "url": "git://github.com/chilts/mongodb-queue.git" + "url": "git://github.com/reedsy/mongodb-queue.git" }, "bugs": { "url": "http://github.com/chilts/mongodb-queue/issues", diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..6166f39 --- /dev/null +++ b/release.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +VERSION=$(node -p "require('./package.json').version") + +git config --local user.email "github@reedsy.com" +git config --local user.name "GitHub Action" +git fetch --tags + +VERSION_COUNT=$(git tag --list $VERSION | wc -l) + +if [ $VERSION_COUNT -gt 0 ] +then + echo "Version $VERSION already deployed." + exit 0 +else + echo "Deploying version $VERSION" +fi + +git tag $VERSION +git push origin refs/tags/$VERSION + +npm publish diff --git a/test/_timeout.js b/test/_timeout.js new file mode 100644 index 0000000..c87a13e --- /dev/null +++ b/test/_timeout.js @@ -0,0 +1,7 @@ +function timeout(millis) { + return new Promise((resolve) => setTimeout(resolve, millis)); +} + +module.exports = { + timeout, +}; diff --git a/test/clean.js b/test/clean.js index 9f56d54..d034865 100644 --- a/test/clean.js +++ b/test/clean.js @@ -1,172 +1,43 @@ -var async = require('async') -var test = require('tape') +const test = require('tape'); -var setup = require('./setup.js') -var mongoDbQueue = require('../') +const setup = require('./setup.js'); +const {MongoDBQueue} = require('../'); -setup(function(client, db) { +setup().then(({client, db}) => { + test('clean: check deleted messages are deleted', async function(t) { + const q = new MongoDBQueue(db, 'clean', {visibility: 3}); - test('clean: check deleted messages are deleted', function(t) { - var queue = mongoDbQueue(db, 'clean', { visibility : 3 }) - var msg + t.equal(await q.size(), 0, 'There is currently nothing on the queue'); + t.equal(await q.total(), 0, 'There is currently nothing in the queue at all'); + await q.clean(); + t.equal(await q.size(), 0, 'There is currently nothing on the queue'); + t.equal(await q.total(), 0, 'There is currently nothing in the queue at all'); + await q.add('Hello, World!'); + await q.clean(); + t.equal(await q.size(), 1, 'Queue size is correct'); + t.equal(await q.total(), 1, 'Queue total is correct'); + const msg = await q.get(); + t.ok(msg.id, 'Got a msg.id (sanity check)'); + t.equal(await q.size(), 0, 'Queue size is correct'); + t.equal(await q.total(), 1, 'Queue total is correct'); + await q.clean(); + t.equal(await q.size(), 0, 'Queue size is correct'); + t.equal(await q.total(), 1, 'Queue total is correct'); + const id = await q.ack(msg.ack); + t.ok(id, 'Received an id when acking this message'); + t.equal(await q.size(), 0, 'Queue size is correct'); + t.equal(await q.total(), 1, 'Queue total is correct'); + await q.clean(); + t.equal(await q.size(), 0, 'Queue size is correct'); + t.equal(await q.total(), 0, 'Queue total is correct'); - async.series( - [ - function(next) { - queue.size(function(err, size) { - t.ok(!err, 'There is no error.') - t.equal(size, 0, 'There is currently nothing on the queue') - next() - }) - }, - function(next) { - queue.total(function(err, size) { - t.ok(!err, 'There is no error.') - t.equal(size, 0, 'There is currently nothing in the queue at all') - next() - }) - }, - function(next) { - queue.clean(function(err) { - t.ok(!err, 'There is no error.') - next() - }) - }, - function(next) { - queue.size(function(err, size) { - t.ok(!err, 'There is no error.') - t.equal(size, 0, 'There is currently nothing on the queue') - next() - }) - }, - function(next) { - queue.total(function(err, size) { - t.ok(!err, 'There is no error.') - t.equal(size, 0, 'There is currently nothing in the queue at all') - next() - }) - }, - function(next) { - queue.add('Hello, World!', function(err) { - t.ok(!err, 'There is no error when adding a message.') - next() - }) - }, - function(next) { - queue.clean(function(err) { - t.ok(!err, 'There is no error.') - next() - }) - }, - function(next) { - queue.size(function(err, size) { - t.ok(!err, 'There is no error.') - t.equal(size, 1, 'Queue size is correct') - next() - }) - }, - function(next) { - queue.total(function(err, size) { - t.ok(!err, 'There is no error.') - t.equal(size, 1, 'Queue total is correct') - next() - }) - }, - function(next) { - queue.get(function(err, newMsg) { - msg = newMsg - t.ok(msg.id, 'Got a msg.id (sanity check)') - next() - }) - }, - function(next) { - queue.size(function(err, size) { - t.ok(!err, 'There is no error.') - t.equal(size, 0, 'Queue size is correct') - next() - }) - }, - function(next) { - queue.total(function(err, size) { - t.ok(!err, 'There is no error.') - t.equal(size, 1, 'Queue total is correct') - next() - }) - }, - function(next) { - queue.clean(function(err) { - t.ok(!err, 'There is no error.') - next() - }) - }, - function(next) { - queue.size(function(err, size) { - t.ok(!err, 'There is no error.') - t.equal(size, 0, 'Queue size is correct') - next() - }) - }, - function(next) { - queue.total(function(err, size) { - t.ok(!err, 'There is no error.') - t.equal(size, 1, 'Queue total is correct') - next() - }) - }, - function(next) { - queue.ack(msg.ack, function(err, id) { - t.ok(!err, 'No error when acking the message') - t.ok(id, 'Received an id when acking this message') - next() - }) - }, - function(next) { - queue.size(function(err, size) { - t.ok(!err, 'There is no error.') - t.equal(size, 0, 'Queue size is correct') - next() - }) - }, - function(next) { - queue.total(function(err, size) { - t.ok(!err, 'There is no error.') - t.equal(size, 1, 'Queue total is correct') - next() - }) - }, - function(next) { - queue.clean(function(err) { - t.ok(!err, 'There is no error.') - next() - }) - }, - function(next) { - queue.size(function(err, size) { - t.ok(!err, 'There is no error.') - t.equal(size, 0, 'Queue size is correct') - next() - }) - }, - function(next) { - queue.total(function(err, size) { - t.ok(!err, 'There is no error.') - t.equal(size, 0, 'Queue total is correct') - next() - }) - }, - ], - function(err) { - if (err) t.fail(err) - t.pass('Finished test ok') - t.end() - } - ) - }) + t.pass('Finished test ok'); + t.end(); + }); - test('client.close()', function(t) { - t.pass('client.close()') - client.close() - t.end() - }) - -}) + test('client.close()', function(t) { + t.pass('client.close()'); + client.close(); + t.end(); + }); +}); diff --git a/test/dead-queue.js b/test/dead-queue.js index 8c4a38b..f81ffea 100644 --- a/test/dead-queue.js +++ b/test/dead-queue.js @@ -1,181 +1,48 @@ -var async = require('async') -var test = require('tape') - -var setup = require('./setup.js') -var mongoDbQueue = require('../') - -setup(function(client, db) { - - test('first test', function(t) { - var queue = mongoDbQueue(db, 'queue', { visibility : 3, deadQueue : 'dead-queue' }) - t.ok(queue, 'Queue created ok') - t.end() - }); - - test('single message going over 5 tries, should appear on dead-queue', function(t) { - var deadQueue = mongoDbQueue(db, 'dead-queue') - var queue = mongoDbQueue(db, 'queue', { visibility : 1, deadQueue : deadQueue }) - var msg - var origId - - async.series( - [ - function(next) { - queue.add('Hello, World!', function(err, id) { - t.ok(!err, 'There is no error when adding a message.') - t.ok(id, 'Received an id for this message') - origId = id - next() - }) - }, - function(next) { - queue.get(function(err, thisMsg) { - setTimeout(function() { - t.pass('First expiration') - next() - }, 2 * 1000) - }) - }, - function(next) { - queue.get(function(err, thisMsg) { - setTimeout(function() { - t.pass('Second expiration') - next() - }, 2 * 1000) - }) - }, - function(next) { - queue.get(function(err, thisMsg) { - setTimeout(function() { - t.pass('Third expiration') - next() - }, 2 * 1000) - }) - }, - function(next) { - queue.get(function(err, thisMsg) { - setTimeout(function() { - t.pass('Fourth expiration') - next() - }, 2 * 1000) - }) - }, - function(next) { - queue.get(function(err, thisMsg) { - setTimeout(function() { - t.pass('Fifth expiration') - next() - }, 2 * 1000) - }) - }, - function(next) { - queue.get(function(err, id) { - t.ok(!err, 'No error when getting no messages') - t.ok(!msg, 'No msg received') - next() - }) - }, - function(next) { - deadQueue.get(function(err, msg) { - t.ok(!err, 'No error when getting from the deadQueue') - t.ok(msg.id, 'Got a message id from the deadQueue') - t.equal(msg.payload.id, origId, 'Got the same message id as the original message') - t.equal(msg.payload.payload, 'Hello, World!', 'Got the same as the original message') - t.equal(msg.payload.tries, 6, 'Got the tries as 6') - next() - }) - }, - ], - function(err) { - t.ok(!err, 'No error during single round-trip test') - t.end() - } - ) - }) - - test('two messages, with first going over 3 tries', function(t) { - var deadQueue = mongoDbQueue(db, 'dead-queue-2') - var queue = mongoDbQueue(db, 'queue-2', { visibility : 1, deadQueue : deadQueue, maxRetries : 3 }) - var msg - var origId, origId2 - - async.series( - [ - function(next) { - queue.add('Hello, World!', function(err, id) { - t.ok(!err, 'There is no error when adding a message.') - t.ok(id, 'Received an id for this message') - origId = id - next() - }) - }, - function(next) { - queue.add('Part II', function(err, id) { - t.ok(!err, 'There is no error when adding another message.') - t.ok(id, 'Received an id for this message') - origId2 = id - next() - }) - }, - function(next) { - queue.get(function(err, thisMsg) { - t.equal(thisMsg.id, origId, 'We return the first message on first go') - setTimeout(function() { - t.pass('First expiration') - next() - }, 2 * 1000) - }) - }, - function(next) { - queue.get(function(err, thisMsg) { - t.equal(thisMsg.id, origId, 'We return the first message on second go') - setTimeout(function() { - t.pass('Second expiration') - next() - }, 2 * 1000) - }) - }, - function(next) { - queue.get(function(err, thisMsg) { - t.equal(thisMsg.id, origId, 'We return the first message on third go') - setTimeout(function() { - t.pass('Third expiration') - next() - }, 2 * 1000) - }) - }, - function(next) { - // This is the 4th time, so we SHOULD have moved it to the dead queue - // pior to it being returned. - queue.get(function(err, msg) { - t.ok(!err, 'No error when getting the 2nd message') - t.equal(msg.id, origId2, 'Got the ID of the 2nd message') - t.equal(msg.payload, 'Part II', 'Got the same payload as the 2nd message') - next() - }) - }, - function(next) { - deadQueue.get(function(err, msg) { - t.ok(!err, 'No error when getting from the deadQueue') - t.ok(msg.id, 'Got a message id from the deadQueue') - t.equal(msg.payload.id, origId, 'Got the same message id as the original message') - t.equal(msg.payload.payload, 'Hello, World!', 'Got the same as the original message') - t.equal(msg.payload.tries, 4, 'Got the tries as 4') - next() - }) - }, - ], - function(err) { - t.ok(!err, 'No error during single round-trip test') - t.end() - } - ) - }) - - test('client.close()', function(t) { - t.pass('client.close()') - client.close() - t.end() - }) - -}) +const test = require('tape'); + +const setup = require('./setup.js'); +const {MongoDBQueue} = require('../'); + +setup().then(({client, db}) => { + test('first test', function(t) { + const queue = new MongoDBQueue(db, 'queue', {visibility: 3, deadQueue: 'dead-queue'}); + t.ok(queue, 'Queue created ok'); + t.end(); + }); + + test('single message going over 5 tries, should appear on dead-queue', async function(t) { + const deadQueue = new MongoDBQueue(db, 'dead-queue'); + const queue = new MongoDBQueue(db, 'queue', {visibility: 1, deadQueue: deadQueue}); + let msg; + + const origId = await queue.add('Hello, World!'); + t.ok(origId, 'Received an id for this message'); + + await queue.get(); + + for (let i = 1; i <= 5; i++) { + await queue.get(); + await new Promise((resolve) => setTimeout(function() { + t.pass(`Expiration #${i}`); + resolve(); + }, 2 * 1000)); + } + + msg = await queue.get(); + t.ok(!msg, 'No msg received'); + + msg = await deadQueue.get(); + t.ok(msg.id, 'Got a message id from the deadQueue'); + t.equal(msg.payload.id, origId, 'Got the same message id as the original message'); + t.equal(msg.payload.payload, 'Hello, World!', 'Got the same as the original message'); + t.equal(msg.payload.tries, 6, 'Got the tries as 6'); + + t.end(); + }); + + test('client.close()', function(t) { + t.pass('client.close()'); + client.close(); + t.end(); + }); +}); diff --git a/test/default.js b/test/default.js index 460f46d..75d1603 100644 --- a/test/default.js +++ b/test/default.js @@ -1,113 +1,75 @@ -var async = require('async') -var test = require('tape') +const test = require('tape'); -var setup = require('./setup.js') -var mongoDbQueue = require('../') +const setup = require('./setup.js'); +const {MongoDBQueue} = require('../'); -setup(function(client, db) { +setup().then(({client, db}) => { + test('first test', function(t) { + const queue = new MongoDBQueue(db, 'default'); + t.ok(queue, 'Queue created ok'); + t.end(); + }); - test('first test', function(t) { - var queue = mongoDbQueue(db, 'default') - t.ok(queue, 'Queue created ok') - t.end() - }); + test('single round trip', async function(t) { + const queue = new MongoDBQueue(db, 'default'); + let id; - test('single round trip', function(t) { - var queue = mongoDbQueue(db, 'default') - var msg + id = await queue.add('Hello, World!'); + t.ok(id, 'Received an id for this message'); - async.series( - [ - function(next) { - queue.add('Hello, World!', function(err, id) { - t.ok(!err, 'There is no error when adding a message.') - t.ok(id, 'Received an id for this message') - next() - }) - }, - function(next) { - queue.get(function(err, thisMsg) { - console.log(thisMsg) - msg = thisMsg - t.ok(msg.id, 'Got a msg.id') - t.equal(typeof msg.id, 'string', 'msg.id is a string') - t.ok(msg.ack, 'Got a msg.ack') - t.equal(typeof msg.ack, 'string', 'msg.ack is a string') - t.ok(msg.tries, 'Got a msg.tries') - t.equal(typeof msg.tries, 'number', 'msg.tries is a number') - t.equal(msg.tries, 1, 'msg.tries is currently one') - t.equal(msg.payload, 'Hello, World!', 'Payload is correct') - next() - }) - }, - function(next) { - queue.ack(msg.ack, function(err, id) { - t.ok(!err, 'No error when acking the message') - t.ok(id, 'Received an id when acking this message') - next() - }) - }, - ], - function(err) { - t.ok(!err, 'No error during single round-trip test') - t.end() - } - ) - }) + const msg = await queue.get(); + t.ok(msg.id, 'Got a msg.id'); + t.equal(typeof msg.id, 'string', 'msg.id is a string'); + t.ok(msg.ack, 'Got a msg.ack'); + t.equal(typeof msg.ack, 'string', 'msg.ack is a string'); + t.ok(msg.tries, 'Got a msg.tries'); + t.equal(typeof msg.tries, 'number', 'msg.tries is a number'); + t.equal(msg.tries, 1, 'msg.tries is currently one'); + t.equal(msg.payload, 'Hello, World!', 'Payload is correct'); - test("single round trip, can't be acked again", function(t) { - var queue = mongoDbQueue(db, 'default') - var msg + id = await queue.ack(msg.ack); + t.ok(id, 'Received an id when acking this message'); + t.end(); + }); - async.series( - [ - function(next) { - queue.add('Hello, World!', function(err, id) { - t.ok(!err, 'There is no error when adding a message.') - t.ok(id, 'Received an id for this message') - next() - }) - }, - function(next) { - queue.get(function(err, thisMsg) { - msg = thisMsg - t.ok(msg.id, 'Got a msg.id') - t.equal(typeof msg.id, 'string', 'msg.id is a string') - t.ok(msg.ack, 'Got a msg.ack') - t.equal(typeof msg.ack, 'string', 'msg.ack is a string') - t.ok(msg.tries, 'Got a msg.tries') - t.equal(typeof msg.tries, 'number', 'msg.tries is a number') - t.equal(msg.tries, 1, 'msg.tries is currently one') - t.equal(msg.payload, 'Hello, World!', 'Payload is correct') - next() - }) - }, - function(next) { - queue.ack(msg.ack, function(err, id) { - t.ok(!err, 'No error when acking the message') - t.ok(id, 'Received an id when acking this message') - next() - }) - }, - function(next) { - queue.ack(msg.ack, function(err, id) { - t.ok(err, 'There is an error when acking the message again') - t.ok(!id, 'No id received when trying to ack an already deleted message') - next() - }) - }, - ], - function(err) { - t.ok(!err, 'No error during single round-trip when trying to double ack') - t.end() - } - ) - }) + test("single round trip, can't be acked again", async function(t) { + const queue = new MongoDBQueue(db, 'default'); + let id; - test('client.close()', function(t) { - t.pass('client.close()') - client.close() - t.end() - }) + id = await queue.add('Hello, World!'); + t.ok(id, 'Received an id for this message'); + const msg = await queue.get(); + t.ok(msg.id, 'Got a msg.id'); + t.equal(typeof msg.id, 'string', 'msg.id is a string'); + t.ok(msg.ack, 'Got a msg.ack'); + t.equal(typeof msg.ack, 'string', 'msg.ack is a string'); + t.ok(msg.tries, 'Got a msg.tries'); + t.equal(typeof msg.tries, 'number', 'msg.tries is a number'); + t.equal(msg.tries, 1, 'msg.tries is currently one'); + t.equal(msg.payload, 'Hello, World!', 'Payload is correct'); + id = await queue.ack(msg.ack); + t.ok(id, 'Received an id when acking this message'); + id = await queue.ack(msg.ack) + .catch((err) => t.ok(err, 'There is an error when acking the message again')); -}) + t.ok(!id, 'No id received when trying to ack an already deleted message'); + t.end(); + }); + + test('remove undefined properties', async function(t) { + const queue = new MongoDBQueue(db, 'default'); + const id = await queue.add({text: 'Hello, World!', undefinedProp: undefined}); + t.ok(id, 'Received an id for this message'); + + const msg = await queue.get(); + t.ok(msg.id, 'Got a msg.id'); + t.equal('undefinedProp' in msg.payload, false, 'Payload has undefinedProp and it should be removed'); + t.end(); + }); + + test('client.close()', function(t) { + t.pass('client.close()'); + client.close(); + t.end(); + }); +}); diff --git a/test/delay.js b/test/delay.js index d1c74ef..bafe991 100644 --- a/test/delay.js +++ b/test/delay.js @@ -1,107 +1,56 @@ -var async = require('async') -var test = require('tape') - -var setup = require('./setup.js') -var mongoDbQueue = require('../') - -setup(function(client, db) { - - test('delay: check messages on this queue are returned after the delay', function(t) { - var queue = mongoDbQueue(db, 'delay', { delay : 3 }) - - async.series( - [ - function(next) { - queue.add('Hello, World!', function(err, id) { - t.ok(!err, 'There is no error when adding a message.') - t.ok(id, 'There is an id returned when adding a message.') - next() - }) - }, - function(next) { - // get something now and it shouldn't be there - queue.get(function(err, msg) { - t.ok(!err, 'No error when getting no messages') - t.ok(!msg, 'No msg received') - // now wait 4s - setTimeout(next, 4 * 1000) - }) - }, - function(next) { - // get something now and it SHOULD be there - queue.get(function(err, msg) { - t.ok(!err, 'No error when getting a message') - t.ok(msg.id, 'Got a message id now that the message delay has passed') - queue.ack(msg.ack, next) - }) - }, - function(next) { - queue.get(function(err, msg) { - // no more messages - t.ok(!err, 'No error when getting no messages') - t.ok(!msg, 'No more messages') - next() - }) - }, - ], - function(err) { - if (err) t.fail(err) - t.pass('Finished test ok') - t.end() - } - ) - }) - - test('delay: check an individual message delay overrides the queue delay', function(t) { - var queue = mongoDbQueue(db, 'delay') - - async.series( - [ - function(next) { - queue.add('I am delayed by 3 seconds', { delay : 3 }, function(err, id) { - t.ok(!err, 'There is no error when adding a message.') - t.ok(id, 'There is an id returned when adding a message.') - next() - }) - }, - function(next) { - // get something now and it shouldn't be there - queue.get(function(err, msg) { - t.ok(!err, 'No error when getting no messages') - t.ok(!msg, 'No msg received') - // now wait 4s - setTimeout(next, 4 * 1000) - }) - }, - function(next) { - // get something now and it SHOULD be there - queue.get(function(err, msg) { - t.ok(!err, 'No error when getting a message') - t.ok(msg.id, 'Got a message id now that the message delay has passed') - queue.ack(msg.ack, next) - }) - }, - function(next) { - queue.get(function(err, msg) { - // no more messages - t.ok(!err, 'No error when getting no messages') - t.ok(!msg, 'No more messages') - next() - }) - }, - ], - function(err) { - if (err) t.fail(err) - t.pass('Finished test ok') - t.end() - } - ) - }) - - test('client.close()', function(t) { - t.pass('client.close()') - client.close() - t.end() - }) - -}) +const test = require('tape'); + +const setup = require('./setup.js'); +const {MongoDBQueue} = require('../'); +const {timeout} = require('./_timeout.js'); + +setup().then(({client, db}) => { + test('delay: check messages on this queue are returned after the delay', async function(t) { + const queue = new MongoDBQueue(db, 'delay', {delay: 3}); + let msg; + + const id = await queue.add('Hello, World!'); + t.ok(id, 'There is an id returned when adding a message.'); + // get something now and it shouldn't be there + msg = await queue.get(); + t.ok(!msg, 'No msg received'); + await timeout(4000); + // get something now and it SHOULD be there + msg = await queue.get(); + t.ok(msg.id, 'Got a message id now that the message delay has passed'); + await queue.ack(msg.ack); + msg = await queue.get(); + // no more messages + t.ok(!msg, 'No more messages'); + t.pass('Finished test ok'); + t.end(); + }); + + test('delay: check an individual message delay overrides the queue delay', async function(t) { + const queue = new MongoDBQueue(db, 'delay'); + let msg; + + const id = await queue.add('I am delayed by 3 seconds', {delay: 3}); + t.ok(id, 'There is an id returned when adding a message.'); + // get something now and it shouldn't be there + msg = await queue.get(); + t.ok(!msg, 'No msg received'); + await timeout(4000); + // get something now and it SHOULD be there + msg = await queue.get(); + t.ok(msg.id, 'Got a message id now that the message delay has passed'); + await queue.ack(msg.ack); + msg = await queue.get(); + // no more messages + t.ok(!msg, 'No more messages'); + + t.pass('Finished test ok'); + t.end(); + }); + + test('client.close()', function(t) { + t.pass('client.close()'); + client.close(); + t.end(); + }); +}); diff --git a/test/indexes.js b/test/indexes.js index 5724afc..1fa50e6 100644 --- a/test/indexes.js +++ b/test/indexes.js @@ -1,27 +1,20 @@ -var async = require('async') -var test = require('tape') +const test = require('tape'); -var setup = require('./setup.js') -var mongoDbQueue = require('../') +const setup = require('./setup.js'); +const {MongoDBQueue} = require('../'); -setup(function(client, db) { +setup().then(({client, db}) => { + test('visibility: check message is back in queue after 3s', async function(t) { + const queue = new MongoDBQueue(db, 'visibility', {visibility: 3}); - test('visibility: check message is back in queue after 3s', function(t) { - t.plan(2) + await queue.createIndexes(); + t.pass('Indexes created'); + t.end(); + }); - var queue = mongoDbQueue(db, 'visibility', { visibility : 3 }) - - queue.createIndexes(function(err, indexName) { - t.ok(!err, 'There was no error when running .ensureIndexes()') - t.ok(indexName, 'receive indexName we created') - t.end() - }) - }) - - test('client.close()', function(t) { - t.pass('client.close()') - client.close() - t.end() - }) - -}) + test('client.close()', function(t) { + t.pass('client.close()'); + client.close(); + t.end(); + }); +}); diff --git a/test/many.js b/test/many.js index b95e907..143e32d 100644 --- a/test/many.js +++ b/test/many.js @@ -1,83 +1,50 @@ -var async = require('async') -var test = require('tape') - -var setup = require('./setup.js') -var mongoDbQueue = require('../') - -var total = 250 - -setup(function(client, db) { - - test('many: add ' + total + ' messages, get ' + total + ' back', function(t) { - var queue = mongoDbQueue(db, 'many') - var msgs = [] - var msgsToQueue = [] - - async.series( - [ - function(next) { - var i - for(i=0; i { + test('many: add ' + total + ' messages, get ' + total + ' back', async function(t) { + const queue = new MongoDBQueue(db, 'many'); + const msgs = []; + const msgsToQueue = []; + + for (let i = 0; i < total; i++) { + msgsToQueue.push('no=' + i); + } + await queue.add(msgsToQueue); + t.pass('All ' + total + ' messages sent to MongoDB'); + + async function getOne() { + const msg = await queue.get(); + if (!msg) return t.fail('Failed getting a message'); + msgs.push(msg); + if (msgs.length !== total) return getOne(); + t.pass('Received all ' + total + ' messages'); + } + await getOne(); + + await Promise.all( + msgs.map((msg) => queue.ack(msg.ack)), + ); + + t.pass('Acked all ' + total + ' messages'); + t.pass('Finished test ok'); + t.end(); + }); + + test('many: add no messages, receive err in callback', async function(t) { + const queue = new MongoDBQueue(db, 'many'); + await queue.add([]) + .catch(() => t.pass('got error')); + t.end(); + }); + + test('client.close()', function(t) { + t.pass('client.close()'); + client.close(); + t.end(); + }); +}); diff --git a/test/multi.js b/test/multi.js index d3be71e..d835ecf 100644 --- a/test/multi.js +++ b/test/multi.js @@ -1,74 +1,38 @@ -var async = require('async') -var test = require('tape') +const test = require('tape'); -var setup = require('./setup.js') -var mongoDbQueue = require('../') +const setup = require('./setup.js'); +const {MongoDBQueue} = require('../'); -var total = 250 +const total = 250; -setup(function(client, db) { +setup().then(({client, db}) => { + test('multi: add ' + total + ' messages, get ' + total + ' back', async function(t) { + const queue = new MongoDBQueue(db, 'multi'); + const msgs = []; - test('multi: add ' + total + ' messages, get ' + total + ' back', function(t) { - var queue = mongoDbQueue(db, 'multi') - var msgs = [] + for (let i = 0; i < total; i++) await queue.add('no=' + i); + t.pass('All ' + total + ' messages sent to MongoDB'); - async.series( - [ - function(next) { - var i, done = 0 - for(i=0; i queue.ack(msg.ack)), + ); -}) + t.pass('Acked all ' + total + ' messages'); + + t.end(); + }); + + test('client.close()', function(t) { + t.pass('client.close()'); + client.close(); + t.end(); + }); +}); diff --git a/test/ping.js b/test/ping.js index 298786a..eedca1a 100644 --- a/test/ping.js +++ b/test/ping.js @@ -1,183 +1,144 @@ -var async = require('async') -var test = require('tape') - -var setup = require('./setup.js') -var mongoDbQueue = require('../') - -setup(function(client, db) { - - test('ping: check a retrieved message with a ping can still be acked', function(t) { - var queue = mongoDbQueue(db, 'ping', { visibility : 5 }) - var msg - - async.series( - [ - function(next) { - queue.add('Hello, World!', function(err, id) { - t.ok(!err, 'There is no error when adding a message.') - t.ok(id, 'There is an id returned when adding a message.') - next() - }) - }, - function(next) { - // get something now and it shouldn't be there - queue.get(function(err, thisMsg) { - msg = thisMsg - t.ok(!err, 'No error when getting this message') - t.ok(msg.id, 'Got this message id') - // now wait 4s - setTimeout(next, 4 * 1000) - }) - }, - function(next) { - // ping this message so it will be kept alive longer, another 5s - queue.ping(msg.ack, function(err, id) { - t.ok(!err, 'No error when pinging a message') - t.ok(id, 'Received an id when acking this message') - // now wait 4s - setTimeout(next, 4 * 1000) - }) - }, - function(next) { - queue.ack(msg.ack, function(err, id) { - t.ok(!err, 'No error when acking this message') - t.ok(id, 'Received an id when acking this message') - next() - }) - }, - function(next) { - queue.get(function(err, msg) { - t.ok(!err, 'No error when getting no messages') - t.ok(!msg, 'No message when getting from an empty queue') - next() - }) - }, - ], - function(err) { - if (err) t.fail(err) - t.pass('Finished test ok') - t.end() - } - ) - }) - - test("ping: check that an acked message can't be pinged", function(t) { - var queue = mongoDbQueue(db, 'ping', { visibility : 5 }) - var msg - - async.series( - [ - function(next) { - queue.add('Hello, World!', function(err, id) { - t.ok(!err, 'There is no error when adding a message.') - t.ok(id, 'There is an id returned when adding a message.') - next() - }) - }, - function(next) { - // get something now and it shouldn't be there - queue.get(function(err, thisMsg) { - msg = thisMsg - t.ok(!err, 'No error when getting this message') - t.ok(msg.id, 'Got this message id') - next() - }) - }, - function(next) { - // ack the message - queue.ack(msg.ack, function(err, id) { - t.ok(!err, 'No error when acking this message') - t.ok(id, 'Received an id when acking this message') - next() - }) - }, - function(next) { - // ping this message, even though it has been acked - queue.ping(msg.ack, function(err, id) { - t.ok(err, 'Error when pinging an acked message') - t.ok(!id, 'Received no id when pinging an acked message') - next() - }) - }, - ], - function(err) { - if (err) t.fail(err) - t.pass('Finished test ok') - t.end() - } - ) - }) - -test("ping: check visibility option overrides the queue visibility", function(t) { - var queue = mongoDbQueue(db, 'ping', { visibility : 3 }) - var msg - - async.series( - [ - function(next) { - queue.add('Hello, World!', function(err, id) { - t.ok(!err, 'There is no error when adding a message.') - t.ok(id, 'There is an id returned when adding a message.') - next() - }) - }, - function(next) { - queue.get(function(err, thisMsg) { - msg = thisMsg - // message should reset in three seconds - t.ok(msg.id, 'Got a msg.id (sanity check)') - setTimeout(next, 2 * 1000) - }) - }, - function(next) { - // ping this message so it will be kept alive longer, another 5s instead of 3s - queue.ping(msg.ack, { visibility: 5 }, function(err, id) { - t.ok(!err, 'No error when pinging a message') - t.ok(id, 'Received an id when acking this message') - // wait 4s so the msg would normally have returns to the queue - setTimeout(next, 4 * 1000) - }) - }, - function(next) { - queue.get(function(err, msg) { - // messages should not be back yet - t.ok(!err, 'No error when getting no messages') - t.ok(!msg, 'No msg received') - // wait 2s so the msg should have returns to the queue - setTimeout(next, 2 * 1000) - }) - }, - function(next) { - queue.get(function(err, msg) { - // yes, there should be a message on the queue again - t.ok(msg.id, 'Got a msg.id (sanity check)') - queue.ack(msg.ack, function(err) { - t.ok(!err, 'No error when acking the message') - next() - }) - }) - }, - function(next) { - queue.get(function(err, msg) { - // no more messages - t.ok(!err, 'No error when getting no messages') - t.ok(!msg, 'No msg received') - next() - }) - } - ], - function(err) { - if (err) t.fail(err) - t.pass('Finished test ok') - t.end() - } - ) - }) - - test('client.close()', function(t) { - t.pass('client.close()') - client.close() - t.end() - }) - -}) +const test = require('tape'); +const {timeout} = require('./_timeout'); + +const setup = require('./setup.js'); +const {MongoDBQueue} = require('../'); + +setup().then(({client, db}) => { + test('ping: check a retrieved message with a ping can still be acked', async function(t) { + const queue = new MongoDBQueue(db, 'ping', {visibility: 5}); + let msg; + let id; + + id = await queue.add('Hello, World!'); + t.ok(id, 'There is an id returned when adding a message.'); + // get something now and it shouldn't be there + msg = await queue.get(); + t.ok(msg.id, 'Got this message id'); + await timeout(4000); + // ping this message so it will be kept alive longer, another 5s + id = await queue.ping(msg.ack); + t.ok(id, 'Received an id when acking this message'); + await timeout(4000); + id = await queue.ack(msg.ack); + t.ok(id, 'Received an id when acking this message'); + msg = await queue.get(); + t.ok(!msg, 'No message when getting from an empty queue'); + + t.pass('Finished test ok'); + t.end(); + }); + + test("ping: check that an acked message can't be pinged", async function(t) { + const queue = new MongoDBQueue(db, 'ping', {visibility: 5}); + let id; + + id = await queue.add('Hello, World!'); + t.ok(id, 'There is an id returned when adding a message.'); + // get something now and it shouldn't be there + const msg = await queue.get(); + t.ok(msg.id, 'Got this message id'); + // ack the message + id = await queue.ack(msg.ack); + t.ok(id, 'Received an id when acking this message'); + // ping this message, even though it has been acked + id = await queue.ping(msg.ack) + .catch((err) => t.ok(err, 'Error when pinging an acked message')); + t.ok(!id, 'Received no id when pinging an acked message'); + + t.pass('Finished test ok'); + t.end(); + }); + + test('ping: check visibility option overrides the queue visibility', async function(t) { + const queue = new MongoDBQueue(db, 'ping', {visibility: 3}); + let msg; + let id; + + id = await queue.add('Hello, World!'); + t.ok(id, 'There is an id returned when adding a message.'); + msg = await queue.get(); + // message should reset in three seconds + t.ok(msg.id, 'Got a msg.id (sanity check)'); + await timeout(2000); + // ping this message so it will be kept alive longer, another 5s instead of 3s + id = await queue.ping(msg.ack, {visibility: 5}); + t.ok(id, 'Received an id when acking this message'); + // wait 4s so the msg would normally have returns to the queue + await timeout(4000); + msg = await queue.get(); + // messages should not be back yet + t.ok(!msg, 'No msg received'); + // wait 2s so the msg should have returns to the queue + await timeout(2000); + msg = await queue.get(); + // yes, there should be a message on the queue again + t.ok(msg.id, 'Got a msg.id (sanity check)'); + await queue.ack(msg.ack); + msg = await queue.get(); + // no more messages + t.ok(!msg, 'No msg received'); + + t.pass('Finished test ok'); + t.end(); + }); + + test('ping: reset tries', async function(t) { + const queue = new MongoDBQueue(db, 'ping', {visibility: 3}); + let msg; + let id; + + id = await queue.add('Hello, World!'); + t.ok(id, 'There is an id returned when adding a message.'); + msg = await queue.get(); + // message should reset in three seconds + t.ok(msg.id, 'Got a msg.id (sanity check)'); + await timeout(2000); + id = await queue.ping(msg.ack, {resetTries: true}); + t.ok(id, 'Received an id when acking this message'); + // wait until the msg has returned to the queue + await timeout(6000); + msg = await queue.get(); + t.equal(msg.tries, 1, 'Tries were reset'); + await queue.ack(msg.ack); + msg = await queue.get(); + // no more messages + t.ok(!msg, 'No msg received'); + + t.pass('Finished test ok'); + t.end(); + }); + + test('ping: reset ack', async function(t) { + const queue = new MongoDBQueue(db, 'ping', {visibility: 3}); + let msg; + let id; + + id = await queue.add('Hello, World!'); + t.ok(id, 'There is an id returned when adding a message.'); + msg = await queue.get(); + const ack = msg.ack; + // message should reset in three seconds + t.ok(msg.id, 'Got a msg.id (sanity check)'); + await timeout(2000); + id = await queue.ping(msg.ack, {resetAck: true}); + t.ok(id, 'Received an id when acking this message'); + // wait until the msg has returned to the queue + await timeout(6000); + msg = await queue.get(); + t.notEqual(ack, msg.ack, 'Ack was reset'); + await queue.ack(msg.ack); + msg = await queue.get(); + // no more messages + t.ok(!msg, 'No msg received'); + + t.pass('Finished test ok'); + t.end(); + }); + + test('client.close()', function(t) { + t.pass('client.close()'); + client.close(); + t.end(); + }); +}); diff --git a/test/setup.js b/test/setup.js index ea893bc..6efb66a 100644 --- a/test/setup.js +++ b/test/setup.js @@ -1,7 +1,7 @@ -const mongodb = require('mongodb') +const mongodb = require(process.env.MONGO_DRIVER || 'mongodb'); -const url = 'mongodb://localhost:27017/' -const dbName = 'mongodb-queue' +const url = 'mongodb://localhost:27017/'; +const dbName = 'mongodb-queue'; const collections = [ 'default', @@ -16,27 +16,16 @@ const collections = [ 'dead-queue', 'queue-2', 'dead-queue-2', -] +]; -module.exports = function(callback) { - const client = new mongodb.MongoClient(url, { useNewUrlParser: true }) +module.exports = async function() { + const client = new mongodb.MongoClient(url, {useNewUrlParser: true}); - client.connect(err => { - // we can throw since this is test-only - if (err) throw err + await client.connect(); + const db = client.db(dbName); - const db = client.db(dbName) - - // empty out some collections to make sure there are no messages - let done = 0 - collections.forEach((col) => { - db.collection(col).deleteMany(() => { - done += 1 - if ( done === collections.length ) { - callback(client, db) - } - }) - }) - }) - -} + await Promise.all( + collections.map((col) => db.collection(col).deleteMany()), + ); + return {client, db}; +}; diff --git a/test/stats.js b/test/stats.js index fab3fa0..3ee7fb8 100644 --- a/test/stats.js +++ b/test/stats.js @@ -1,208 +1,67 @@ -var async = require('async') -var test = require('tape') +const test = require('tape'); +const {timeout} = require('./_timeout'); -var setup = require('./setup.js') -var mongoDbQueue = require('../') +const setup = require('./setup.js'); +const {MongoDBQueue} = require('../'); -setup(function(client, db) { +setup().then(({client, db}) => { + test('first test', function(t) { + const queue = new MongoDBQueue(db, 'stats'); + t.ok(queue, 'Queue created ok'); + t.end(); + }); - test('first test', function(t) { - var queue = mongoDbQueue(db, 'stats') - t.ok(queue, 'Queue created ok') - t.end() - }); + test('stats for a single message added, received and acked', async function(t) { + const q = new MongoDBQueue(db, 'stats1'); - test('stats for a single message added, received and acked', function(t) { - var queue = mongoDbQueue(db, 'stats1') - var msg + const id = await q.add('Hello, World!'); + t.ok(id, 'Received an id for this message'); + t.equal(await q.total(), 1, 'Total number of messages is one'); + t.equal(await q.size(), 1, 'Size of queue is one'); + t.equal(await q.inFlight(), 0, 'There are no inFlight messages'); + t.equal(await q.done(), 0, 'There are no done messages'); + const msg = await q.get(); + t.equal(await q.total(), 1, 'Total number of messages is still one'); + t.equal(await q.size(), 0, 'Size of queue is now zero (ie. none to come)'); + t.equal(await q.inFlight(), 1, 'There is one inflight message'); + t.equal(await q.done(), 0, 'There are still no done messages'); + // now ack that message + await q.ack(msg.ack); + t.equal(await q.total(), 1, 'Total number of messages is again one'); + t.equal(await q.size(), 0, 'Size of queue is still zero (ie. none to come)'); + t.equal(await q.inFlight(), 0, 'There are no inflight messages anymore'); + t.equal(await q.done(), 1, 'There is now one processed message'); - async.series( - [ - function(next) { - queue.add('Hello, World!', function(err, id) { - t.ok(!err, 'There is no error when adding a message.') - t.ok(id, 'Received an id for this message') - next() - }) - }, - function(next) { - queue.total(function(err, count) { - t.equal(count, 1, 'Total number of messages is one') - next() - }) - }, - function(next) { - queue.size(function(err, count) { - t.equal(count, 1, 'Size of queue is one') - next() - }) - }, - function(next) { - queue.inFlight(function(err, count) { - t.equal(count, 0, 'There are no inFlight messages') - next() - }) - }, - function(next) { - queue.done(function(err, count) { - t.equal(count, 0, 'There are no done messages') - next() - }) - }, - function(next) { - // let's set one to be inFlight - queue.get(function(err, newMsg) { - msg = newMsg - next() - }) - }, - function(next) { - queue.total(function(err, count) { - t.equal(count, 1, 'Total number of messages is still one') - next() - }) - }, - function(next) { - queue.size(function(err, count) { - t.equal(count, 0, 'Size of queue is now zero (ie. none to come)') - next() - }) - }, - function(next) { - queue.inFlight(function(err, count) { - t.equal(count, 1, 'There is one inflight message') - next() - }) - }, - function(next) { - queue.done(function(err, count) { - t.equal(count, 0, 'There are still no done messages') - next() - }) - }, - function(next) { - // now ack that message - queue.ack(msg.ack, function(err, newMsg) { - msg = newMsg - next() - }) - }, - function(next) { - queue.total(function(err, count) { - t.equal(count, 1, 'Total number of messages is again one') - next() - }) - }, - function(next) { - queue.size(function(err, count) { - t.equal(count, 0, 'Size of queue is still zero (ie. none to come)') - next() - }) - }, - function(next) { - queue.inFlight(function(err, count) { - t.equal(count, 0, 'There are no inflight messages anymore') - next() - }) - }, - function(next) { - queue.done(function(err, count) { - t.equal(count, 1, 'There is now one processed message') - next() - }) - }, - ], - function(err) { - t.ok(!err, 'No error when doing stats on one message') - t.end() - } - ) - }) + t.end(); + }); + // ToDo: add more tests for adding a message, getting it and letting it lapse + // then re-checking all stats. - // ToDo: add more tests for adding a message, getting it and letting it lapse - // then re-checking all stats. + test('stats for a single message added, received, timed-out and back on queue', async function(t) { + const q = new MongoDBQueue(db, 'stats2', {visibility: 3}); - test('stats for a single message added, received, timed-out and back on queue', function(t) { - var queue = mongoDbQueue(db, 'stats2', { visibility : 3 }) + const id = await q.add('Hello, World!'); + t.ok(id, 'Received an id for this message'); + t.equal(await q.total(), 1, 'Total number of messages is one'); + t.equal(await q.size(), 1, 'Size of queue is one'); + t.equal(await q.inFlight(), 0, 'There are no inFlight messages'); + t.equal(await q.done(), 0, 'There are no done messages'); + // let's set one to be inFlight + await q.get(); + // msg is ignored, we don't care about the message here + await timeout(4000); + t.equal(await q.total(), 1, 'Total number of messages is still one'); + t.equal(await q.size(), 1, 'Size of queue is still at one'); + t.equal(await q.inFlight(), 0, 'There are no inflight messages again'); + t.equal(await q.done(), 0, 'There are still no done messages'); - async.series( - [ - function(next) { - queue.add('Hello, World!', function(err, id) { - t.ok(!err, 'There is no error when adding a message.') - t.ok(id, 'Received an id for this message') - next() - }) - }, - function(next) { - queue.total(function(err, count) { - t.equal(count, 1, 'Total number of messages is one') - next() - }) - }, - function(next) { - queue.size(function(err, count) { - t.equal(count, 1, 'Size of queue is one') - next() - }) - }, - function(next) { - queue.inFlight(function(err, count) { - t.equal(count, 0, 'There are no inFlight messages') - next() - }) - }, - function(next) { - queue.done(function(err, count) { - t.equal(count, 0, 'There are no done messages') - next() - }) - }, - function(next) { - // let's set one to be inFlight - queue.get(function(err, msg) { - // msg is ignored, we don't care about the message here - setTimeout(next, 4 * 1000) - }) - }, - function(next) { - queue.total(function(err, count) { - t.equal(count, 1, 'Total number of messages is still one') - next() - }) - }, - function(next) { - queue.size(function(err, count) { - t.equal(count, 1, 'Size of queue is still at one') - next() - }) - }, - function(next) { - queue.inFlight(function(err, count) { - t.equal(count, 0, 'There are no inflight messages again') - next() - }) - }, - function(next) { - queue.done(function(err, count) { - t.equal(count, 0, 'There are still no done messages') - next() - }) - }, - ], - function(err) { - t.ok(!err, 'No error when doing stats on one message') - t.end() - } - ) - }) - - test('client.close()', function(t) { - t.pass('client.close()') - client.close() - t.end() - }) - -}) + t.end(); + }); + test('client.close()', function(t) { + t.pass('client.close()'); + client.close(); + t.end(); + }); +}); diff --git a/test/visibility.js b/test/visibility.js index 56fda8c..a5cd631 100644 --- a/test/visibility.js +++ b/test/visibility.js @@ -1,174 +1,90 @@ -var async = require('async') -var test = require('tape') +const test = require('tape'); +const {timeout} = require('./_timeout'); -var setup = require('./setup.js') -var mongoDbQueue = require('../') +const setup = require('./setup.js'); +const {MongoDBQueue} = require('../'); -setup(function(client, db) { +setup().then(({client, db}) => { + test('visibility: check message is back in queue after 3s', async function(t) { + const queue = new MongoDBQueue(db, 'visibility', {visibility: 3}); + let msg; - test('visibility: check message is back in queue after 3s', function(t) { - var queue = mongoDbQueue(db, 'visibility', { visibility : 3 }) + await queue.add('Hello, World!'); + msg = await queue.get(); + t.ok(msg.id, 'Got a msg.id (sanity check)'); + await timeout(4000); + msg = await queue.get(); + // yes, there should be a message on the queue again + t.ok(msg.id, 'Got a msg.id (sanity check)'); + await queue.ack(msg.ack); + msg = await queue.get(); + t.ok(!msg, 'No msg received'); - async.series( - [ - function(next) { - queue.add('Hello, World!', function(err) { - t.ok(!err, 'There is no error when adding a message.') - next() - }) - }, - function(next) { - queue.get(function(err, msg) { - // wait over 3s so the msg returns to the queue - t.ok(msg.id, 'Got a msg.id (sanity check)') - setTimeout(next, 4 * 1000) - }) - }, - function(next) { - queue.get(function(err, msg) { - // yes, there should be a message on the queue again - t.ok(msg.id, 'Got a msg.id (sanity check)') - queue.ack(msg.ack, function(err) { - t.ok(!err, 'No error when acking the message') - next() - }) - }) - }, - function(next) { - queue.get(function(err, msg) { - // no more messages - t.ok(!err, 'No error when getting no messages') - t.ok(!msg, 'No msg received') - next() - }) - }, - ], - function(err) { - if (err) t.fail(err) - t.pass('Finished test ok') - t.end() - } - ) - }) + t.pass('Finished test ok'); + t.end(); + }); - test("visibility: check that a late ack doesn't remove the msg", function(t) { - var queue = mongoDbQueue(db, 'visibility', { visibility : 3 }) - var originalAck + test("visibility: check that a late ack doesn't remove the msg", async function(t) { + const queue = new MongoDBQueue(db, 'visibility', {visibility: 3}); + let msg; - async.series( - [ - function(next) { - queue.add('Hello, World!', function(err) { - t.ok(!err, 'There is no error when adding a message.') - next() - }) - }, - function(next) { - queue.get(function(err, msg) { - t.ok(msg.id, 'Got a msg.id (sanity check)') + await queue.add('Hello, World!'); + msg = await queue.get(); + t.ok(msg.id, 'Got a msg.id (sanity check)'); + // remember this original ack + const originalAck = msg.ack; + // wait over 3s so the msg returns to the queue + await timeout(4000); - // remember this original ack - originalAck = msg.ack + t.pass('Back from timeout, now acking the message'); - // wait over 3s so the msg returns to the queue - setTimeout(function() { - t.pass('Back from timeout, now acking the message') + // now ack the message but too late - it shouldn't be deleted + msg = await queue.ack(msg.ack) + .catch((err) => t.ok(err, 'Got an error when acking the message late')); + t.ok(!msg, 'No message was updated'); + msg = await queue.get(); + // the message should now be able to be retrieved, with a new 'ack' id + t.ok(msg.id, 'Got a msg.id (sanity check)'); + t.notEqual(msg.ack, originalAck, 'Original ack and new ack are different'); - // now ack the message but too late - it shouldn't be deleted - queue.ack(msg.ack, function(err, msg) { - t.ok(err, 'Got an error when acking the message late') - t.ok(!msg, 'No message was updated') - next() - }) - }, 4 * 1000) - }) - }, - function(next) { - queue.get(function(err, msg) { - // the message should now be able to be retrieved, with a new 'ack' id - t.ok(msg.id, 'Got a msg.id (sanity check)') - t.notEqual(msg.ack, originalAck, 'Original ack and new ack are different') + // now ack this new retrieval + await queue.ack(msg.ack); + msg = await queue.get(); - // now ack this new retrieval - queue.ack(msg.ack, next) - }) - }, - function(next) { - queue.get(function(err, msg) { - // no more messages - t.ok(!err, 'No error when getting no messages') - t.ok(!msg, 'No msg received') - next() - }) - }, - ], - function(err) { - if (err) t.fail(err) - t.pass('Finished test ok') - t.end() - } - ) - }) + // no more messages + t.ok(!msg, 'No msg received'); - test("visibility: check visibility option overrides the queue visibility", function(t) { - var queue = mongoDbQueue(db, 'visibility', { visibility : 2 }) - var originalAck + t.pass('Finished test ok'); + t.end(); + }); - async.series( - [ - function(next) { - queue.add('Hello, World!', function(err) { - t.ok(!err, 'There is no error when adding a message.') - next() - }) - }, - function(next) { - queue.get({ visibility: 4 }, function(err, msg) { - // wait over 2s so the msg would normally have returns to the queue - t.ok(msg.id, 'Got a msg.id (sanity check)') - setTimeout(next, 3 * 1000) - }) - }, - function(next) { - queue.get(function(err, msg) { - // messages should not be back yet - t.ok(!err, 'No error when getting no messages') - t.ok(!msg, 'No msg received') - // wait 2s so the msg should have returns to the queue - setTimeout(next, 2 * 1000) - }) - }, - function(next) { - queue.get(function(err, msg) { - // yes, there should be a message on the queue again - t.ok(msg.id, 'Got a msg.id (sanity check)') - queue.ack(msg.ack, function(err) { - t.ok(!err, 'No error when acking the message') - next() - }) - }) - }, - function(next) { - queue.get(function(err, msg) { - // no more messages - t.ok(!err, 'No error when getting no messages') - t.ok(!msg, 'No msg received') - next() - }) - } - ], - function(err) { - if (err) t.fail(err) - t.pass('Finished test ok') - t.end() - } - ) - }) + test('visibility: check visibility option overrides the queue visibility', async function(t) { + const queue = new MongoDBQueue(db, 'visibility', {visibility: 2}); + let msg; - test('client.close()', function(t) { - t.pass('client.close()') - client.close() - t.end() - }) + await queue.add('Hello, World!'); + msg = await queue.get({visibility: 4}); + t.ok(msg.id, 'Got a msg.id (sanity check)'); + // wait over 2s so the msg would normally have returns to the queue + await timeout(3000); + msg = await queue.get(); + t.ok(!msg, 'No msg received'); + // wait 2s so the msg should have returns to the queue + await timeout(2000); + msg = await queue.get(); + t.ok(msg.id, 'Got a msg.id (sanity check)'); + await queue.ack(msg.ack); + msg = await queue.get(); + // no more messages + t.ok(!msg, 'No msg received'); -}) + t.pass('Finished test ok'); + t.end(); + }); + + test('client.close()', function(t) { + t.pass('client.close()'); + client.close(); + t.end(); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..77858df --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "noImplicitAny": true, + "noUnusedLocals": true, + "moduleResolution": "node", + "sourceMap": true, + "declaration": true, + }, + "exclude": [ + "test/" + ], +}