Source: mongodb.js

var Db = require('mongodb').Db,
    Connection = require('mongodb').Connection,
    Server = require('mongodb').Server,
    Q = require('q'),
    _ = require('underscore-data'),
    BaseBackend = require('./base');




/** @class MongoBackend
    @extends BaseBackend */
var MongoBackend = BaseBackend.extend(
/** @lends MongoBackend# */
{
    /** @method */
    initialize: function(options) {
        options = _.extend({}, MongoBackend.defaults, options || {});
        this.options = options;

        this._db = new Db(options.database, new Server(options.host, options.port,  {auto_reconnect: true})/*, {native_parser:true}*/);

        this._opened = null;
    },

    /** @method */
    objectStoreNames: function() {
        return this.open().then(function(db) {
            return Q.ninvoke(db, 'collectionNames');
        });
    },

    /** @method */
    objectStore: function(name, options) {
        return new MongoStore(this, name, options);
    },

    /** @method */
    createObjectStore: function(name, options) {
        return this.open().then(function(db) {
            return Q.ninvoke(db, 'collection', name).then(function() {
                return new MongoStore(this, name, options);
            });
        });
    },

    /** @method */
    deleteObjectStore: function(name) {
        return this.open().then(function(db) {
            return Q.ninvoke(db, 'dropCollection', name);
        });
    },

    /** @method */
    open: function() {
        var self = this;
        if (!this._opened) {
            this._opened = Q.ninvoke(this._db, 'open')
                .then(function (db) {
                    if (self.options.username) {
                        return Q.ninvoke(db, 'authenticate', self.options.username, self.options.password)
                                .then(function() {
                                    return db;
                                });
                    } else {
                        return db;
                    }
                });
        }
        return this._opened;
    },

    /** @method */
    close: function() {
        if (this._opened) {
            this._opened = false;
            return Q.ninvoke(this._db, 'close');
        }
    },

    /** @method */
    isClosed: function() {
        return Q.defer()
                .resolve(!!this._opened);
    }
});

MongoBackend.defaults = {
    host: 'localhost',
    port: 27017,
    database: 'default'
};

/** @class MongoStore
    @extends BaseStore */
var MongoStore = BaseBackend.BaseStore.extend(
/** @lends MongoStore# */
{
    /** @method */
    initialize: function(backend, name, options) {
        options = _.extend({keyPath: '_id'}, options || {});
        BaseBackend.BaseStore.prototype.initialize.call(this, backend, name, options);

        this._collection = null;
    },

    /** @method */
    get: function(directives) {
        var search = {};
        search[this.keyPath] = this._getObjectKey({}, directives);
        return this.collection().then(function(collection) {
            return Q.ninvoke(collection, 'findOne', search);
        });
    },

    /** @method */
    add: function(object, directives) {
        //var key = this._getObjectKey(object, directives);

        return this.collection().then(function(collection) {
            return Q.ninvoke(collection, 'insert', object, {safe:true})
                    .then(function(result) {
                        return result[0] || object;
                    });
        });
    },

    /** @method */
    put: function(object, directives) {
        var key = this._getObjectKey(object, directives),
            selector = {};
        selector[this.keyPath] = key;
        return this.collection().then(function(collection) {
            return Q.ninvoke(collection, 'update', selector, object, {safe:true})
                    .then(function(result) {
                        return object;
                    });
        });
    },

    /** @method */
    'delete': function(directives) {
        var search = {};
        search[this.keyPath] = this._getObjectKey({}, directives);
        return this.collection().then(function(collection) {
            return Q.ninvoke(collection, 'remove', search, {safe:true, single: true});
        });
    },

    /** Execute RQL query */
    query: function(query) {
        var meta = {},
            search = {};
        if (query && !_.isEmpty(query)) {
            var x = this.parse(query);
            search = x.search;
            meta = x.meta;
        }

        return this.collection().then(function(collection) {
            return Q.ninvoke(collection, 'find', search||{}, meta||{})
                    .then(function(cursor) {
                        return Q.ninvoke(cursor, 'toArray');
                    });
        });
    },

    /** Get native DB object */
    db: function() {
        return this.backend.open();
    },

    /** Get Collection object */
    collection: function() {
        if (!this._collection) {
            var self = this;
            this._collection = this.db().then(function(db) {
                return Q.ninvoke(db, 'collection', self.name);
            });
        }
        return this._collection;
    },

    /** Delete all items */
    clear: function() {
        return this.collection().then(function(collection) {
            return Q.ninvoke(collection, 'remove', {}, {safe: true});
        });
    },

    /** Parse RQL query */
    parse: function(query) {
        return _.rql(query).toMongo();
    },

    /* @method */
    _getObjectKey: function(obj, key) {
        if (typeof key === 'object') {
            key = key.key;
        }

        key =  key || obj[this.keyPath];

        var intKey = parseInt(key, 10);
        if (!isNaN(intKey) && intKey.toString() === key) {
            key = intKey;
        }

        return key;
    }
});

/** @module mongodb */

MongoBackend.MongoStore = MongoStore;
module.exports = MongoBackend;