Last active
October 8, 2015 01:18
-
-
Save andresmatasuarez/343d5ff39bb1a208a046 to your computer and use it in GitHub Desktop.
NodeJS | Module: mongo-db-utils | Promisified (with Bluebird) connection handler for Mongoose connections.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @module mongo-db-utils | |
* @desc Promisified (with Bluebird) connection handler for Mongoose connections. | |
* @see {@link https://gist.github.com/andresmatasuarez/343d5ff39bb1a208a046| GitHub gist} | |
* @author Andrés Mata Suárez <amatasuarez@gmail> | |
* @license {@link http://www.opensource.org/licenses/mit-license.php| MIT License} | |
* | |
* @requires {@link https://github.com/visionmedia/debug|debug} | |
* @requires {@link https://github.com/petkaantonov/bluebird|bluebird} | |
* @requires {@link http://mongoosejs.com/|mongoose} | |
* | |
* @example | |
* var MongoDBUtils = require('mongo-db-utils'); | |
* | |
* var db = new MongoDBUtils('mongodb://localhost/database'); | |
* db.connect() | |
* .then(function(uri){ | |
* console.log('Connected to ' + uri); | |
* return db.disconnect(); | |
* }) | |
* .then(function(uri){ | |
* console.log('Disconnected from ' + uri); | |
* }); | |
* | |
*/ | |
'use strict'; | |
var DEBUG_CONNECTING = 'Connecting to %s...'; | |
var DEBUG_ALREADY_CONNECTED = 'Already connected to %s.'; | |
var DEBUG_ALREADY_CONNECTING = 'Already connecting to %s.'; | |
var DEBUG_CONNECTED = 'Successfully connected to %s.'; | |
var DEBUG_CONNECTION_ERROR = 'An error has occured while connecting to %s.'; | |
var DEBUG_DISCONNECTING = 'Disconnecting from %s...'; | |
var DEBUG_ALREADY_DISCONNECTED = 'Already disconnected from %s.'; | |
var DEBUG_ALREADY_DISCONNECTING = 'Already disconnecting from %s.'; | |
var DEBUG_DISCONNECTED = 'Successfully disconnected from %s.'; | |
var DEBUG_DISCONNECTION_ERROR = 'An error has occured while disconnecting from %s.'; | |
var BB = require('bluebird'); | |
var mongoose = require('mongoose'); | |
var debug = require('debug'); | |
var d = debug('mongo-db-utils'); | |
var isState = function(state){ | |
return mongoose.connection.readyState === mongoose.Connection.STATES[state]; | |
}; | |
/** | |
* @constructor | |
* | |
* @param {string} uri - Mongoose connection URI. | |
* @param {object} options - Mongoose connection options. | |
* @see http://mongoosejs.com/docs/connections.html | |
* | |
*/ | |
function MongoDBUtils(uri, options){ | |
this.uri = uri; | |
this.options = options; | |
} | |
/** | |
* Add connection listeners without adding more than one for each event. | |
* This is done to avoid: | |
* 'warning: possible EventEmitter memory leak detected. 11 listeners added' | |
* More info: https://github.com/joyent/node/issues/5108 | |
*/ | |
MongoDBUtils.prototype.addConnectionListener = function(event, cb){ | |
var listeners = mongoose.connection._events; | |
if (!listeners || !listeners[event] || listeners[event].length === 0){ | |
mongoose.connection.once(event, cb.bind(this)); | |
} | |
}; | |
/** | |
* Returns a promise that gets resolved when successfully connected to MongoDB URI, or rejected otherwise. | |
*/ | |
MongoDBUtils.prototype.connect = function(){ | |
return new BB(function(resolve, reject){ | |
if (isState('connected')){ | |
d(DEBUG_ALREADY_CONNECTED, this.uri); | |
return resolve(this.uri); | |
} | |
this.addConnectionListener('error', function(err){ | |
d(DEBUG_CONNECTION_ERROR, this.uri); | |
return reject(err); | |
}); | |
this.addConnectionListener('open', function(){ | |
d(DEBUG_CONNECTED, this.uri); | |
return resolve(this.uri); | |
}); | |
if (isState('connecting')){ | |
d(DEBUG_ALREADY_CONNECTING, this.uri); | |
} else { | |
d(DEBUG_CONNECTING, this.uri); | |
mongoose.connect(this.uri, this.options); | |
} | |
}.bind(this)); | |
}; | |
/** | |
* Returns a promise that gets resolved when successfully disconnected from MongoDB URI, or rejected otherwise. | |
*/ | |
MongoDBUtils.prototype.disconnect = function(){ | |
return new BB(function(resolve, reject){ | |
if (isState('disconnected') || isState('uninitialized')){ | |
d(DEBUG_ALREADY_DISCONNECTED, this.uri); | |
return resolve(this.uri); | |
} | |
this.addConnectionListener('error', function(err){ | |
d(DEBUG_DISCONNECTION_ERROR, this.uri); | |
return reject(err); | |
}); | |
this.addConnectionListener('disconnected', function(){ | |
d(DEBUG_DISCONNECTED, this.uri); | |
return resolve(this.uri); | |
}); | |
if (isState('disconnecting')){ | |
d(DEBUG_ALREADY_DISCONNECTING, this.uri); | |
} else { | |
d(DEBUG_DISCONNECTING, this.uri); | |
mongoose.disconnect(); | |
} | |
}.bind(this)); | |
}; | |
module.exports = MongoDBUtils; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
var url = require('url'); | |
var config = require('config'); | |
var expect = require('chai').expect; | |
var mongoose = require('mongoose'); | |
var MongoDBUtils = require('mongo-db-utils'); | |
var fullUri = config.db; | |
var DEFAULT_PORT = 27017; | |
var uri = { | |
full : fullUri, | |
host : url.parse(fullUri).hostname, | |
port : url.parse(fullUri).port || DEFAULT_PORT, | |
name : url.parse(fullUri).pathname.replace('/', '') | |
}; | |
var finish = function(done){ | |
return function(){ | |
done(); | |
}; | |
}; | |
var ensureDisconnected = function(done){ | |
if (mongoose.connection.readyState === mongoose.Connection.STATES.disconnected || | |
mongoose.connection.readyState === mongoose.Connection.STATES.uninitialized){ | |
return done(); | |
} | |
mongoose.connection.once('disconnected', function(){ | |
done(); | |
}); | |
mongoose.disconnect(); | |
}; | |
describe('Module: mongo-db-utils', function(){ | |
before(ensureDisconnected); | |
afterEach(ensureDisconnected); | |
it('should connect to database', function(done){ | |
var db = new MongoDBUtils(uri.full); | |
db.connect() | |
.then(function(connectionURI){ | |
expect(connectionURI) .to.equal(uri.full); | |
expect(mongoose.connection.readyState) .to.equal(mongoose.Connection.STATES.connected); | |
expect(mongoose.connection.db.serverConfig.host) .to.equal(uri.host); | |
expect(mongoose.connection.db.serverConfig.port) .to.equal(uri.port); | |
expect(mongoose.connection.db.databaseName) .to.equal(uri.name); | |
}) | |
.then(finish(done)) | |
.catch(done); | |
}); | |
it('should disconnect from database', function(done){ | |
mongoose.connect(uri.full); | |
mongoose.connection.once('open', function(){ | |
var db = new MongoDBUtils(uri.full); | |
db.disconnect() | |
.then(function(){ | |
expect(mongoose.connection.readyState).to.equal(mongoose.Connection.STATES.disconnected); | |
}) | |
.then(finish(done)) | |
.catch(done); | |
}); | |
}); | |
it('should detect a previously opened connection and not try to open a new one', function(done){ | |
var db = new MongoDBUtils(uri.full); | |
db.connect() | |
.then(function(){ return db.connect(); }) | |
.then(function(){ | |
expect(mongoose.connection.readyState).to.equal(mongoose.Connection.STATES.connected); | |
}) | |
.then(finish(done)) | |
.catch(done); | |
}); | |
it('should detect an unopened connection and not try to disconnect from it', function(done){ | |
var db = new MongoDBUtils(uri.full); | |
db.disconnect() | |
.then(function(){ | |
expect(mongoose.connection.readyState).to.equal(mongoose.Connection.STATES.disconnected); | |
}) | |
.then(finish(done)) | |
.catch(done); | |
}); | |
it('should connect, then disconnect, then connect again', function(done){ | |
var db = new MongoDBUtils(uri.full); | |
db.connect() | |
.then(function(){ return db.disconnect(); }) | |
.then(function(){ return db.connect(); }) | |
.then(function(){ | |
expect(mongoose.connection.readyState).to.equal(mongoose.Connection.STATES.connected); | |
}) | |
.then(finish(done)) | |
.catch(done); | |
}); | |
it('should connect, then disconnect, then connect again, then disconnect again', function(done){ | |
var db = new MongoDBUtils(uri.full); | |
db.connect() | |
.then(function(){ return db.disconnect(); }) | |
.then(function(){ return db.connect(); }) | |
.then(function(){ return db.disconnect(); }) | |
.then(function(){ | |
expect(mongoose.connection.readyState).to.equal(mongoose.Connection.STATES.disconnected); | |
}) | |
.then(finish(done)) | |
.catch(done); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Fix event emitter excepton handling.
Take ideas from: http://www.jongleberry.com/understanding-possible-eventemitter-leaks.html