diff --git a/.travis.yml b/.travis.yml index ddcb0f18..24395319 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,18 @@ language: node_js node_js: - - "6" - - "8" + - "12" - "10" + - "8" services: - docker env: - - MONGODB_VERSION="2.6" - - MONGODB_VERSION="3.6" + - MONGODB_VERSION="4.2" - MONGODB_VERSION="4.0" + - MONGODB_VERSION="3.6" + - MONGODB_VERSION="3.4" before_install: - docker run -d -p 127.0.0.1:27017:27017 mongo:$MONGODB_VERSION diff --git a/README.md b/README.md index f606ce46..d4d9bc17 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ together with snapshots. ## Usage -`sharedb-mongo` wraps native [mongodb](https://github.com/mongodb/node-mongodb-native), and it supports the same configuration options. +`sharedb-mongo` uses the [MongoDB NodeJS Driver](https://github.com/mongodb/node-mongodb-native), and it supports the same configuration options. There are two ways to instantiate a sharedb-mongo wrapper: @@ -33,7 +33,7 @@ There are two ways to instantiate a sharedb-mongo wrapper: arguments as arguments to the module function. For example: ```javascript - const db = require('sharedb-mongo')('mongodb://localhost:27017/test'); + const db = require('sharedb-mongo')('mongodb://localhost:27017/test', {mongoOptions: {...}}); const backend = new ShareDB({db}); ``` diff --git a/index.js b/index.js index db1e4b2a..ad9e8dfa 100644 --- a/index.js +++ b/index.js @@ -58,7 +58,9 @@ function ShareDbMongo(mongo, options) { // We can only get the mongodb client instance in a callback, so // buffer up any requests received in the meantime this.mongo = null; + this._mongoClient = null; this.mongoPoll = null; + this._mongoPollClient = null; this.pendingConnect = []; this._connect(mongo, options); } else { @@ -125,6 +127,16 @@ ShareDbMongo.prototype._flushPendingConnect = function() { } }; +function isLegacyMongoClient(client) { + // mongodb 2.0 connect returns a DB object that also implements the + // functionality of client, such as `close()`. mongodb 3.0 connect returns a + // Client without the `collection()` method + return ( + typeof client.collection === 'function' && + typeof client.close === 'function' + ); +} + ShareDbMongo.prototype._connect = function(mongo, options) { // Create the mongo connection client connections if needed // @@ -137,32 +149,49 @@ ShareDbMongo.prototype._connect = function(mongo, options) { tasks = {mongo: mongo, mongoPoll: options.mongoPoll}; } else { tasks = { - mongo: function(parallelCb) { + mongoClient: function(parallelCb) { mongodb.connect(mongo, options.mongoOptions, parallelCb); }, - mongoPoll: function(parallelCb) { + mongoPollClient: function(parallelCb) { mongodb.connect(options.mongoPoll, options.mongoPollOptions, parallelCb); } }; } async.parallel(tasks, function(err, results) { if (err) throw err; - self.mongo = results.mongo; - self.mongoPoll = results.mongoPoll; + var mongoClient = results.mongoClient; + var mongoPollClient = results.mongoPollClient; + if (isLegacyMongoClient(mongoClient)) { + self.mongo = self._mongoClient = mongoClient; + self.mongoPoll = self._mongoPollClient = mongoPollClient; + } else { + self.mongo = mongoClient.db(); + self._mongoClient = mongoClient; + self.mongoPoll = mongoPollClient.db(); + self._mongoPollClient = mongoPollClient; + } self._flushPendingConnect(); }); return; } - var finish = function(err, db) { + var finish = function(err, client) { if (err) throw err; - self.mongo = db; + if (isLegacyMongoClient(client)) { + self.mongo = self._mongoClient = client; + } else { + self.mongo = client.db(); + self._mongoClient = client; + } self._flushPendingConnect(); }; if (typeof mongo === 'function') { mongo(finish); return; } - mongodb.connect(mongo, options, finish); + // TODO: Don't pass options directly to mongodb.connect(); + // only pass options.mongoOptions + var mongoOptions = options.mongoOptions || options; + mongodb.connect(mongo, mongoOptions, finish); }; ShareDbMongo.prototype.close = function(callback) { @@ -172,13 +201,13 @@ ShareDbMongo.prototype.close = function(callback) { }; } var self = this; - this.getDbs(function(err, mongo, mongoPoll) { + this.getDbs(function(err) { if (err) return callback(err); self.closed = true; - mongo.close(function(err) { + self._mongoClient.close(function(err) { if (err) return callback(err); - if (!mongoPoll) return callback(); - mongoPoll.close(callback); + if (!self._mongoPollClient) return callback(); + self._mongoPollClient.close(callback); }); }); }; diff --git a/package.json b/package.json index 49e729df..580ae575 100644 --- a/package.json +++ b/package.json @@ -4,25 +4,25 @@ "description": "MongoDB database adapter for ShareDB", "main": "index.js", "dependencies": { - "async": "^1.4.2", - "mongodb": "^2.1.2", + "async": "^1.5.2", + "mongodb": "^2.1.2 || ^3.0.0", "sharedb": "^1.0.0-beta" }, "devDependencies": { - "coveralls": "^2.11.8", + "chai": "^4.2.0", + "coveralls": "^3.0.7", "eslint": "^5.16.0", "eslint-config-google": "^0.13.0", - "expect.js": "^0.3.1", - "istanbul": "^0.4.2", - "mocha": "^2.3.3", - "sharedb-mingo-memory": "^1.0.2", + "mocha": "^6.2.2", + "nyc": "^14.1.1", + "sharedb-mingo-memory": "^1.1.1", "sinon": "^6.1.5" }, "scripts": { "lint": "./node_modules/.bin/eslint --ignore-path .gitignore '**/*.js'", "lint:fix": "npm run lint -- --fix", "test": "npm run lint && node_modules/.bin/mocha", - "test-cover": "npm run lint && node_modules/istanbul/lib/cli.js cover node_modules/mocha/bin/_mocha" + "test-cover": "npm run lint && node_modules/nyc/bin/nyc.js --temp-dir=coverage -r text -r lcov node_modules/mocha/bin/_mocha" }, "repository": "git://github.com/share/sharedb-mongo.git", "author": "Nate Smith and Joseph Gentle", diff --git a/test/test_get_ops_without_strict_linking.js b/test/test_get_ops_without_strict_linking.js index c789946c..9616b9c0 100644 --- a/test/test_get_ops_without_strict_linking.js +++ b/test/test_get_ops_without_strict_linking.js @@ -1,5 +1,4 @@ -var expect = require('expect.js'); -var mongodb = require('mongodb'); +var expect = require('chai').expect; var ShareDbMongo = require('..'); var getQuery = require('sharedb-mingo-memory/get-query'); var sinon = require('sinon'); @@ -7,19 +6,17 @@ var sinon = require('sinon'); var mongoUrl = process.env.TEST_MONGO_URL || 'mongodb://localhost:27017/test'; function create(callback) { - var db = new ShareDbMongo({ - mongo: function(shareDbCallback) { - mongodb.connect(mongoUrl, function(err, mongo) { - if (err) return callback(err); - mongo.dropDatabase(function(err) { - if (err) return callback(err); - shareDbCallback(null, mongo); - callback(null, db, mongo); - }); - }); - }, + var db = new ShareDbMongo(mongoUrl, { + mongoOptions: {}, getOpsWithoutStrictLinking: true }); + db.getDbs(function(err, mongo) { + if (err) return callback(err); + mongo.dropDatabase(function(err) { + if (err) return callback(err); + callback(null, db, mongo); + }); + }); }; require('sharedb/test/db')({create: create, getQuery: getQuery}); @@ -67,11 +64,11 @@ describe('getOpsWithoutStrictLinking: true', function() { it('fetches ops 0-1 without fetching all ops', function(done) { db.getOps(collection, id, 0, 2, null, function(error, ops) { if (error) return done(error); - expect(ops.length).to.be(2); - expect(ops[0].v).to.be(0); - expect(ops[1].v).to.be(1); - expect(db._getSnapshotOpLink.notCalled).to.be(true); - expect(db._getOps.calledOnceWith(collection, id, 0, 2)).to.be(true); + expect(ops.length).to.equal(2); + expect(ops[0].v).to.equal(0); + expect(ops[1].v).to.equal(1); + expect(db._getSnapshotOpLink.notCalled).to.equal(true); + expect(db._getOps.calledOnceWith(collection, id, 0, 2)).to.equal(true); done(); }); }); @@ -81,16 +78,16 @@ describe('getOpsWithoutStrictLinking: true', function() { callInSeries([ function(next) { - mongo.collection('o_' + collection).insert(spuriousOp, next); + mongo.collection('o_' + collection).insertOne(spuriousOp, next); }, function(result, next) { db.getOps(collection, id, 0, 2, null, next); }, function(ops, next) { - expect(ops.length).to.be(2); - expect(ops[1].oi).to.be('bar'); - expect(db._getSnapshotOpLink.notCalled).to.be(true); - expect(db._getOps.calledOnceWith(collection, id, 0, 2)).to.be(true); + expect(ops.length).to.equal(2); + expect(ops[1].oi).to.equal('bar'); + expect(db._getSnapshotOpLink.notCalled).to.equal(true); + expect(db._getOps.calledOnceWith(collection, id, 0, 2)).to.equal(true); next(); }, done @@ -102,16 +99,16 @@ describe('getOpsWithoutStrictLinking: true', function() { callInSeries([ function(next) { - mongo.collection('o_' + collection).insert(spuriousOp, next); + mongo.collection('o_' + collection).insertOne(spuriousOp, next); }, function(result, next) { db.getOps(collection, id, 0, 2, null, next); }, function(ops, next) { - expect(ops.length).to.be(2); - expect(ops[1].oi).to.be('bar'); - expect(db._getSnapshotOpLink.notCalled).to.be(true); - expect(db._getOps.calledOnceWith(collection, id, 0, 3)).to.be(true); + expect(ops.length).to.equal(2); + expect(ops[1].oi).to.equal('bar'); + expect(db._getSnapshotOpLink.notCalled).to.equal(true); + expect(db._getOps.calledOnceWith(collection, id, 0, 3)).to.equal(true); next(); }, done @@ -134,10 +131,10 @@ describe('getOpsWithoutStrictLinking: true', function() { db.getOps(collection, id, 0, 2, null, next); }, function(ops, next) { - expect(ops.length).to.be(2); + expect(ops.length).to.equal(2); expect(ops[0].create).to.eql({}); - expect(ops[1].oi).to.be('bar'); - expect(db._getSnapshotOpLink.calledOnce).to.be(true); + expect(ops[1].oi).to.equal('bar'); + expect(db._getSnapshotOpLink.calledOnce).to.equal(true); next(); }, done diff --git a/test/test_mongo.js b/test/test_mongo.js index 7a75c955..4b8ba60e 100644 --- a/test/test_mongo.js +++ b/test/test_mongo.js @@ -1,21 +1,18 @@ -var expect = require('expect.js'); -var mongodb = require('mongodb'); -var ShareDbMongo = require('../index'); +var expect = require('chai').expect; +var ShareDbMongo = require('..'); var getQuery = require('sharedb-mingo-memory/get-query'); var mongoUrl = process.env.TEST_MONGO_URL || 'mongodb://localhost:27017/test'; function create(callback) { - var db = new ShareDbMongo({mongo: function(shareDbCallback) { - mongodb.connect(mongoUrl, function(err, mongo) { + var db = new ShareDbMongo(mongoUrl); + db.getDbs(function(err, mongo) { + if (err) return callback(err); + mongo.dropDatabase(function(err) { if (err) return callback(err); - mongo.dropDatabase(function(err) { - if (err) return callback(err); - shareDbCallback(null, mongo); - callback(null, db, mongo); - }); + callback(null, db, mongo); }); - }}); + }); }; require('sharedb/test/db')({create: create, getQuery: getQuery}); @@ -43,9 +40,9 @@ describe('mongo db', function() { mongo.collection('o_testcollection').indexInformation(function(err, indexes) { if (err) return done(err); // Index for getting document(s) ops - expect(indexes['d_1_v_1']).ok(); + expect(indexes['d_1_v_1']).ok; // Index for checking committed op(s) by src and seq - expect(indexes['src_1_seq_1_v_1']).ok(); + expect(indexes['src_1_seq_1_v_1']).ok; done(); }); }); @@ -70,9 +67,9 @@ describe('mongo db', function() { it('does not allow editing the system collection', function(done) { var db = this.db; db.commit('system', 'test', {v: 0, create: {}}, {}, null, function(err) { - expect(err).ok(); + expect(err).ok; db.getSnapshot('system', 'test', null, null, function(err) { - expect(err).ok(); + expect(err).ok; done(); }); }); @@ -85,21 +82,21 @@ describe('mongo db', function() { it('does not allow $where queries', function(done) { this.db.query('testcollection', {$where: 'true'}, null, null, function(err) { - expect(err).ok(); + expect(err).ok; done(); }); }); it('queryPollDoc does not allow $where queries', function(done) { this.db.queryPollDoc('testcollection', 'somedoc', {$where: 'true'}, null, function(err) { - expect(err).ok(); + expect(err).ok; done(); }); }); it('$query is deprecated', function(done) { this.db.query('testcollection', {$query: {}}, null, null, function(err) { - expect(err).ok(); + expect(err).ok; expect(err.code).eql(4106); done(); }); @@ -107,7 +104,7 @@ describe('mongo db', function() { it('only one collection operation allowed', function(done) { this.db.query('testcollection', {$distinct: {y: 1}, $aggregate: {}}, null, null, function(err) { - expect(err).ok(); + expect(err).ok; expect(err.code).eql(4108); done(); }); @@ -115,7 +112,7 @@ describe('mongo db', function() { it('only one cursor operation allowed', function(done) { this.db.query('testcollection', {$count: true, $explain: true}, null, null, function(err) { - expect(err).ok(); + expect(err).ok; expect(err.code).eql(4109); done(); }); @@ -123,7 +120,7 @@ describe('mongo db', function() { it('cursor transform can\'t run after collection operation', function(done) { this.db.query('testcollection', {$distinct: {y: 1}, $sort: {y: 1}}, null, null, function(err) { - expect(err).ok(); + expect(err).ok; expect(err.code).eql(4110); done(); }); @@ -131,7 +128,7 @@ describe('mongo db', function() { it('cursor operation can\'t run after collection operation', function(done) { this.db.query('testcollection', {$distinct: {y: 1}, $count: true}, null, null, function(err) { - expect(err).ok(); + expect(err).ok; expect(err.code).eql(4110); done(); }); @@ -139,7 +136,7 @@ describe('mongo db', function() { it('non-object $readPref should return error', function(done) { this.db.query('testcollection', {$readPref: true}, null, null, function(err) { - expect(err).ok(); + expect(err).ok; expect(err.code).eql(4107); done(); }); @@ -148,7 +145,7 @@ describe('mongo db', function() { it('malformed $mapReduce should return error', function(done) { this.db.allowJSQueries = true; // required for $mapReduce this.db.query('testcollection', {$mapReduce: true}, null, null, function(err) { - expect(err).ok(); + expect(err).ok; expect(err.code).eql(4107); done(); }); @@ -255,7 +252,7 @@ describe('mongo db', function() { {$sort: {count: 1}} ]}; this.db.query('testcollection', query, null, null, function(err) { - expect(err).ok(); + expect(err).ok; done(); }); }); @@ -286,7 +283,7 @@ describe('mongo db', function() { } }; db.query('testcollection', query, null, null, function(err) { - expect(err).ok(); + expect(err).ok; done(); }); }); diff --git a/test/test_op_link_validator.js b/test/test_op_link_validator.js index c2366aed..70aa8324 100644 --- a/test/test_op_link_validator.js +++ b/test/test_op_link_validator.js @@ -1,16 +1,16 @@ var OpLinkValidator = require('../op-link-validator'); -var expect = require('expect.js'); +var expect = require('chai').expect; describe('OpLinkValidator', function() { it('starts with no unique op', function() { var validator = new OpLinkValidator(); var opWithUniqueVersion = validator.opWithUniqueVersion(); - expect(opWithUniqueVersion).to.be(null); + expect(opWithUniqueVersion).to.equal(null); }); it('starts not at the end of the list', function() { var validator = new OpLinkValidator(); - expect(validator.isAtEndOfList()).to.be(false); + expect(validator.isAtEndOfList()).to.equal(false); }); it('has no unique op with just one op', function() { @@ -20,7 +20,7 @@ describe('OpLinkValidator', function() { validator.push(op); var opWithUniqueVersion = validator.opWithUniqueVersion(); - expect(opWithUniqueVersion).to.be(null); + expect(opWithUniqueVersion).to.equal(null); }); it('has a unique op with just two different ops', function() { @@ -32,7 +32,7 @@ describe('OpLinkValidator', function() { validator.push(op2); var opWithUniqueVersion = validator.opWithUniqueVersion(); - expect(opWithUniqueVersion).to.be(op1); + expect(opWithUniqueVersion).to.equal(op1); }); it('does not have a uniquye op with just two identical ops', function() { @@ -44,7 +44,7 @@ describe('OpLinkValidator', function() { validator.push(op2); var opWithUniqueVersion = validator.opWithUniqueVersion(); - expect(opWithUniqueVersion).to.be(null); + expect(opWithUniqueVersion).to.equal(null); }); it('has a unique op with three ops with different versions', function() { @@ -58,7 +58,7 @@ describe('OpLinkValidator', function() { validator.push(op3); var opWithUniqueVersion = validator.opWithUniqueVersion(); - expect(opWithUniqueVersion).to.be(op2); + expect(opWithUniqueVersion).to.equal(op2); }); it('is not at the end of the list with three ops', function() { @@ -71,7 +71,7 @@ describe('OpLinkValidator', function() { validator.push(op2); validator.push(op3); - expect(validator.isAtEndOfList()).to.be(false); + expect(validator.isAtEndOfList()).to.equal(false); }); it('does not have a unique op with three ops with the same version', function() { @@ -83,7 +83,7 @@ describe('OpLinkValidator', function() { validator.push(op); var opWithUniqueVersion = validator.opWithUniqueVersion(); - expect(opWithUniqueVersion).to.be(null); + expect(opWithUniqueVersion).to.equal(null); }); it('does not have a unique op if the first two ops are the same', function() { @@ -97,7 +97,7 @@ describe('OpLinkValidator', function() { validator.push(op3); var opWithUniqueVersion = validator.opWithUniqueVersion(); - expect(opWithUniqueVersion).to.be(null); + expect(opWithUniqueVersion).to.equal(null); }); it('does not have a unique op if the last two ops are the same', function() { @@ -111,7 +111,7 @@ describe('OpLinkValidator', function() { validator.push(op3); var opWithUniqueVersion = validator.opWithUniqueVersion(); - expect(opWithUniqueVersion).to.be(null); + expect(opWithUniqueVersion).to.equal(null); }); it('has a unique op in a long chain', function() { @@ -133,7 +133,7 @@ describe('OpLinkValidator', function() { validator.push(op7); var opWithUniqueVersion = validator.opWithUniqueVersion(); - expect(opWithUniqueVersion).to.be(op6); + expect(opWithUniqueVersion).to.equal(op6); }); it('has a unique op with two ops and a current op of null', function() { @@ -147,13 +147,13 @@ describe('OpLinkValidator', function() { validator.push(op3); var opWithUniqueVersion = validator.opWithUniqueVersion(); - expect(opWithUniqueVersion).to.be(op2); + expect(opWithUniqueVersion).to.equal(op2); }); it('is at the end of the list with a current op of null', function() { var op = null; var validator = new OpLinkValidator(); validator.push(op); - expect(validator.isAtEndOfList()).to.be(true); + expect(validator.isAtEndOfList()).to.equal(true); }); }); diff --git a/test/test_skip_poll.js b/test/test_skip_poll.js index fae3a679..587f4058 100644 --- a/test/test_skip_poll.js +++ b/test/test_skip_poll.js @@ -1,4 +1,4 @@ -var expect = require('expect.js'); +var expect = require('chai').expect; var ShareDbMongo = require('../index'); describe('skipPoll', function() {