Skip to content

Instantly share code, notes, and snippets.

@andresmatasuarez
Last active October 8, 2015 01:18
Show Gist options
  • Save andresmatasuarez/343d5ff39bb1a208a046 to your computer and use it in GitHub Desktop.
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.
/**
* @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;
'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);
});
});
@andresmatasuarez
Copy link
Author

Fix event emitter excepton handling.
Take ideas from: http://www.jongleberry.com/understanding-possible-eventemitter-leaks.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment