├── index.js ├── .gitignore ├── .npmignore ├── History.md ├── test ├── mongoq.test.js ├── deferred.test.js ├── session.test.js ├── db.test.js ├── collection.test.js └── util.test.js ├── Makefile ├── package.json ├── examples └── users.js ├── lib ├── cursor.js ├── mongoq.js ├── session.js ├── collection.js ├── db.js └── util.js └── Readme.md /index.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require('./lib/mongoq'); 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | *.swp 3 | .lock-wscript 4 | node_modules 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | support 2 | test 3 | examples 4 | build 5 | .lock-wscript 6 | node_modules 7 | npm-debug.log 8 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 0.0.1 / 2010-01-03 3 | ================== 4 | 5 | * Initial release 6 | 7 | 0.1.0 / 2010-09-15 8 | ================== 9 | 10 | * Rebuild code 11 | * Full supports for connect string 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/mongoq.test.js: -------------------------------------------------------------------------------- 1 | 2 | var mongoq = require('../index.js') 3 | , should = require('should'); 4 | 5 | describe("mongoq", function() { 6 | 7 | it( 'test .version', function(){ 8 | mongoq.version.should.match(/^\d+\.\d+\.\d+$/); 9 | }); 10 | 11 | }); 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | test: 3 | @echo "Please start mongodb[localhost:27017] at first." 4 | @echo "Test mongoq" 5 | @NODE_ENV=test ./node_modules/.bin/mocha --slow 20 --growl \ 6 | ./test/*.test.js 7 | 8 | docs: docs/api.html 9 | 10 | docs/api.html: lib/*.js 11 | dox \ 12 | --private \ 13 | --title Mongoq \ 14 | --desc "" \ 15 | $(shell find lib/* -type f) > $@ 16 | 17 | docclean: 18 | rm -f docs/*.{1,html} 19 | 20 | .PHONY: test 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mongoq" 3 | , "version": "0.2.5" 4 | , "description": "Use mongoDB like this: require('mongoq')('testdb').collection('users').find(function(err, cursor){});" 5 | , "keywords": ["mongodb", "mongoq", "data", "datastore", "nosql"] 6 | , "author" : "Hidden " 7 | , "repository" : { 8 | "type" : "git", 9 | "url" : "http://github.com/zzdhidden/mongoq.git" 10 | } 11 | , "dependencies": { 12 | "mongodb" : "^1.4.35" 13 | , "jquery-deferred": "^0.2.0" 14 | } 15 | , "devDependencies": { 16 | "mocha" : "*" 17 | , "connect": "*" 18 | , "should": ">=0.2.1" 19 | } 20 | , "main": "index" 21 | , "engines": { "node": ">=0.4.0" } 22 | } 23 | -------------------------------------------------------------------------------- /test/deferred.test.js: -------------------------------------------------------------------------------- 1 | var mongoq = require('../index.js') 2 | , should = require('should'); 3 | 4 | describe("deferred", function() { 5 | it("should return a promise object when find", function( done ){ 6 | var users = mongoq("mongoqTest", {auto_reconnect: true}).collection("users23424") 7 | , hadOpen = false; 8 | 9 | users.drop(function() { 10 | users.insert([{name: "jack"}, {name: "lucy"}]).done(function(_users) { 11 | users.find().toArray().done(function(_users) { 12 | should.exist( _users ); 13 | _users.should.have.length( 2 ); 14 | hadOpen = true; 15 | }).done(function(_users) { 16 | _users.should.have.length( 2 ); 17 | //Deferred not supports find().each() 18 | users.find().each().done(function(user) { 19 | 20 | hadOpen.should.be.true; 21 | users.db.close(done); 22 | 23 | }); 24 | }); 25 | }); 26 | }); 27 | 28 | }); 29 | }); 30 | 31 | -------------------------------------------------------------------------------- /examples/users.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Example for insert/update/find/remove users 3 | * 4 | */ 5 | 6 | var assert = require("assert") 7 | , mongoq = require("../index.js") 8 | , db = mongoq("mongodb://127.0.0.1:27017/mongoqTest") 9 | , User = db.collection("users"); 10 | 11 | User.remove() //clear test date 12 | .done( function() { 13 | User.insert( [{ _id: 1, name: "Jack", age: 22 }, { _id: 2, name: "Lucy", age: 20 }] ) //Add Jack and Lucy 14 | .and( User.insert( { _id: 3, name: "Mike", age: 21 } ) ) //Add Mike synchronous 15 | .and( function(u1, u2) { 16 | return User.update({_id: 3}, {$set: {age: 25}}, {safe: 1}); 17 | } ) 18 | .and( function(u1, u2, u3) { 19 | // Will find after add Jack, Lucy and Mike 20 | return User.findOne( { name: u2[0]["name"] } ) 21 | } ) 22 | .done( function(u1, u2, u3, u4) { //All be done 23 | assert.deepEqual( u1, [{ _id: 1, name: "Jack", age: 22 }, { _id: 2, name: "Lucy", age: 20 }], "Insert first" ); 24 | assert.deepEqual( u2, [{ _id: 3, name: "Mike", age: 21 }], "insert second" ); 25 | assert.deepEqual( u4, { _id: 3, name: "Mike", age: 25 }, "Find after insert" ); 26 | db.close(); 27 | } ) 28 | .fail( function( err ) { // Any error occur 29 | console.log( err ); 30 | } ); 31 | } ) 32 | .fail( function( err ) { // Faild to remove 33 | console.log( err ); 34 | } ); 35 | 36 | -------------------------------------------------------------------------------- /test/session.test.js: -------------------------------------------------------------------------------- 1 | var mongoq = require('../index.js') 2 | , should = require('should'); 3 | 4 | describe("session", function() { 5 | 6 | var db = mongoq("mongoqTest"); 7 | 8 | it("should work with normal collection", function( done ) { 9 | var sessions = db.collection("sessions"); 10 | 11 | var store = new mongoq.SessionStore( sessions ); 12 | 13 | store.clear( function(err) { 14 | store.set( "1", {id: 1 }, function( err ) { 15 | should.not.exist( err ); 16 | store.set( "1", {id: 2}, function( err ) { 17 | should.not.exist( err ); 18 | store.get( "1", function(err, sess) { 19 | sess.should.eql( { id: 2 } ); 20 | store.destroy("1", function(err, c) { 21 | should.not.exist( err ); 22 | should.not.exist( c ); 23 | store.get( "1", function(err, sess) { 24 | should.not.exist( sess ); 25 | store.length( function(err, l) { 26 | l.should.equal( 0 ); 27 | db.close(); 28 | done(); 29 | } ); 30 | }); 31 | }); 32 | }); 33 | } ); 34 | } ); 35 | } ); 36 | }); 37 | 38 | it("should work with capped collection", function( done ) { 39 | var sessions2 = db.collection("sessions2", { 40 | capped: true 41 | , max: 4096 42 | , size: 4096 * 8192 43 | , autoIndexId: true 44 | }); 45 | 46 | var store = new mongoq.SessionStore( sessions2 ); 47 | store.clear( function(err) { 48 | store.set( "1", {id: 1 }, function( err ) { 49 | should.not.exist( err ); 50 | store.set( "1", {id: 2}, function( err ) { 51 | should.not.exist( err ); 52 | store.get( "1", function(err, sess) { 53 | sess.should.eql( { id: 2 } ); 54 | store.destroy("1", function(err, c) { 55 | should.not.exist( err ); 56 | should.not.exist( c ); 57 | store.get( "1", function(err, sess) { 58 | should.not.exist( sess ); 59 | store.length( function(err, l) { 60 | l.should.equal( 0 ); 61 | done(); 62 | }); 63 | }); 64 | }); 65 | }); 66 | } ); 67 | } ); 68 | } ); 69 | }); 70 | }); 71 | 72 | -------------------------------------------------------------------------------- /lib/cursor.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Mongoq 3 | * 4 | * Copyright (c) 2011 Hidden 5 | * Released under the MIT, BSD, and GPL Licenses. 6 | * 7 | * Date: 2011-09-11 8 | */ 9 | 10 | /** 11 | * Depends 12 | */ 13 | 14 | var mongodb = require("mongodb") 15 | , util = require('./util') 16 | , EventEmitter = require("events").EventEmitter 17 | , inherits = require("util").inherits 18 | , slice = Array.prototype.slice 19 | , STATE_CLOSE = 0 20 | , STATE_OPENNING = 1 21 | , STATE_OPEN = 2; 22 | 23 | /** 24 | * Mongoq cursor class 25 | * 26 | * @param {Cursor} original 27 | * @param {Collection} collection 28 | * @param {Array} args 29 | * 30 | * @api public 31 | * 32 | */ 33 | 34 | module.exports = cursor; 35 | function cursor(original, collection, args) { 36 | var self = this; 37 | EventEmitter.call(self); 38 | self.setMaxListeners( 0 ); //Disable max listener warning... 39 | self.original = original; 40 | self.collection = collection; 41 | self.args = args; 42 | self.state = STATE_CLOSE; 43 | if ( !original ) { 44 | self.state = STATE_CLOSE; 45 | self.open(); 46 | }else { 47 | self.state = STATE_OPEN; 48 | } 49 | } 50 | 51 | /** 52 | * Enable events 53 | */ 54 | 55 | inherits( cursor, EventEmitter ); 56 | 57 | 58 | /** 59 | * Inherits mongodb.Db methods 60 | * 61 | * rewind() 62 | * toArray(callback) 63 | * each(callback) 64 | * count(callback) 65 | * sort(keyOrList, direction) //=> this 66 | * limit(limit) //=> this 67 | * skip(limit) //=> this 68 | * batchSize(limit) //=> this 69 | * limitRequest// get 70 | * generateQueryCommand 71 | * formattedOrderClause 72 | * formatSortValue 73 | * nextObject(callback) 74 | * getMore(callback) 75 | * explain(callback) 76 | * streamRecords 77 | * close 78 | * isClosed 79 | * 80 | */ 81 | 82 | var getters = ["isClosed", "limitRequest"] 83 | , setters = ["sort", "limit", "skip", "batchSize"] 84 | , ignores = ["rewind", "generateQueryCommand", "formattedOrderClause", "formatSortValue", "streamRecords", "close"]; 85 | util.extend( cursor, mongodb.Cursor, getters, ignores, "original", setters ); 86 | 87 | 88 | /** 89 | * Open collction 90 | * 91 | * @param {Function} callback 92 | * @return {Curor} 93 | * @api public 94 | * 95 | */ 96 | 97 | cursor.prototype.open = function( callback ) { 98 | var self = this; 99 | switch ( this.state ) { 100 | case STATE_OPEN: 101 | callback && callback.call(self, null, self.original); break; 102 | case STATE_OPENNING: 103 | callback && self.once('open', callback); break; 104 | case STATE_CLOSE: 105 | default: 106 | callback && self.once('open', callback); open(); 107 | } 108 | return this; 109 | function open() { 110 | self.state = STATE_OPENNING; 111 | self.collection.open(function(err, collection) { 112 | err ? fail( err ) : done( collection ); 113 | }); 114 | } 115 | 116 | function parseArgs () { 117 | var args = self.args || [] 118 | , len = self.args.length - 1 119 | , callback = ( 'function' === typeof args[len] ) && args[len]; 120 | if (callback) { 121 | self.once('open', callback); 122 | args.pop(); 123 | } 124 | return args; 125 | } 126 | function done ( collection ) { 127 | self.state = STATE_OPEN; 128 | var args = parseArgs(); 129 | 130 | self.original = collection.find.apply(collection, args); 131 | self.emit("open", null, self.original); 132 | } 133 | function fail (err) { 134 | self.state = STATE_CLOSE; 135 | var args = parseArgs(); 136 | self.emit("open", err); 137 | } 138 | } 139 | 140 | -------------------------------------------------------------------------------- /lib/mongoq.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Mongoq 3 | * https://github.com/zzdhidden/mongoq 4 | * 5 | * Copyright (c) 2011 Hidden 6 | * Released under the MIT, BSD, and GPL Licenses. 7 | * 8 | * Date: 2011-09-11 9 | */ 10 | 11 | /*! 12 | * Module dependencies. 13 | */ 14 | 15 | var mongodb = require("mongodb") 16 | , util = require('./util') 17 | , db = require('./db') 18 | , collection = require('./collection') 19 | , cursor = require('./cursor'); 20 | 21 | /** 22 | * Mongoq interface 23 | * @see mongoq 24 | * @api public 25 | */ 26 | 27 | module.exports = mongoq; 28 | 29 | /** 30 | * Library version. 31 | */ 32 | 33 | mongoq.version = JSON.parse(require("fs").readFileSync(require("path").join(__dirname, '..', 'package.json'), 'utf8'))['version']; 34 | 35 | /** 36 | * Link to mongodb. 37 | * @api public 38 | */ 39 | 40 | mongoq.mongodb = mongodb; 41 | 42 | /** 43 | * Link to BSON. 44 | * @api public 45 | */ 46 | 47 | mongoq.BSON = mongodb.BSONPure; 48 | 49 | /** 50 | * Link to util. 51 | * @api public 52 | */ 53 | 54 | mongoq.util = util; 55 | 56 | /** 57 | * Link to db class. 58 | * @api public 59 | */ 60 | 61 | mongoq.db = db; 62 | 63 | /** 64 | * Link to collection class. 65 | * @api public 66 | */ 67 | 68 | mongoq.collection = collection; 69 | 70 | /** 71 | * Link to cursor class. 72 | * @api public 73 | */ 74 | 75 | mongoq.cursor = cursor; 76 | 77 | /** 78 | * Link to SessionStore 79 | * @api public 80 | */ 81 | 82 | mongoq.SessionStore = require("./session"); 83 | 84 | /** 85 | * Connnect to mongodb 86 | * 87 | * Supports connection string. 88 | * 89 | * - mongodb://localhost/testdb 90 | * - mongodb://fred:foobar@localhost:27017/testdb?auto_reconnect=true&poolSize=2 91 | * - mongodb://fred:foobar@localhost:27017,localhost:27018/testdb?reconnectWait=2000;retries=20 92 | * 93 | * The options include server config options and db options 94 | * - `host` the mongodb server host 95 | * - `port` the mongodb server port 96 | * 97 | * @param {String} dbnameOrUrl 98 | * @param {Object} options 99 | * @return {Db} 100 | * @api public 101 | * 102 | */ 103 | 104 | function mongoq( dbnameOrUrl, options ) { 105 | options = options || {}; 106 | var dbname 107 | , url = dbnameOrUrl || "mongodb:\/\/localhost:27017/default" 108 | , query 109 | , urlRE = new RegExp('^mongo(?:db)?://(?:|([^@/]*)@)([^@/]*)(?:|/([^?]*)(?:|\\?([^?]*)))$') 110 | , match = url.match(urlRE); 111 | if( !match ) { 112 | //Check if dbname 113 | var host = options.host || "localhost" 114 | , port = options.port || "27017"; 115 | url = "mongodb:\/\/" + host + ":" + port + "/" + url; 116 | match = url.match(urlRE); 117 | } 118 | if ( !match ) 119 | throw Error("URL must be in the format mongodb:\/\/user:pass@host:port/dbname or dbname"); 120 | 121 | dbname = match[3] || 'default'; 122 | 123 | /** parse query to options */ 124 | if (match[4]) { 125 | query = (match[4] || '').split(/[&;]/); 126 | query.forEach(function(param){ 127 | param = param.split("="); 128 | var key = param[0], val = param[1]; 129 | if ( [undefined, '', 'true', 'false'].indexOf(val) != -1 ) { 130 | options[key] = val != 'false'; 131 | } else if ( parseFloat(val).toString() === val ) { 132 | options[key] = parseFloat(val); 133 | } else { 134 | options[key] = val; 135 | } 136 | }); 137 | } 138 | 139 | /** servers */ 140 | var servers = match[2].split(',').map(function(h) { 141 | var hostPort = h.split(':', 2); 142 | return new mongodb.Server(hostPort[0] || 'localhost', +hostPort[1] || 27017, options); 143 | }); 144 | 145 | var server; 146 | if (servers.length == 1) { 147 | server = servers[0]; 148 | } else { 149 | var op = {}; 150 | //Dup opitons 151 | for( var key in options ) { 152 | op[ key ] = options[ key ]; 153 | } 154 | server = new mongodb.ReplSetServers(servers, op); 155 | } 156 | 157 | /** auth */ 158 | var auth = (match[1] || '').split(':', 2); 159 | if ( auth.length && auth[0] ) { 160 | options.username = auth[0]; 161 | options.password = auth[1]; 162 | } 163 | 164 | return new db(dbname, server, options); 165 | } 166 | 167 | 168 | -------------------------------------------------------------------------------- /lib/session.js: -------------------------------------------------------------------------------- 1 | /** 2 | * connect session store 3 | * 4 | * Use in express or connect project 5 | * 6 | * Examples: 7 | * 8 | * Use in normal collection 9 | * 10 | * app.use(express.session({ 11 | * store: new mongoq.SessionStore( mongoq("mongodb://localhost").collection("sessions") ) 12 | * })); 13 | * 14 | * Use in capped collection with max length 4096 15 | * 16 | * app.use(express.session({ 17 | * store: new mongoq.SessionStore( mongoq("mongodb://localhost").collection("sessions", { 18 | * capped: true, 19 | * max: 4096, 20 | * size: 4096 * 8192 21 | * }) ) 22 | * })); 23 | * 24 | */ 25 | 26 | /** 27 | * Module dependencies 28 | */ 29 | 30 | var Store; 31 | try{ 32 | Store = require('connect').session.Store; 33 | }catch(e){ 34 | try{ 35 | Store = require('express').session.Store; 36 | }catch(e){ 37 | Store = loop; 38 | } 39 | } 40 | 41 | function loop () { 42 | } 43 | 44 | /** 45 | * Initialize SessionStore with the given `options`. 46 | * 47 | * @param {Object} options 48 | * @api public 49 | */ 50 | 51 | var SessionStore = module.exports = function SessionStore( col, options ) { 52 | if( Store === loop ) { 53 | throw new Error("Before using SessionStore, You should install connect or express into your project."); 54 | } 55 | options = options || {}; 56 | Store.call( this, options ); 57 | 58 | this.collection = col; 59 | this.capped = col._options.capped; 60 | }; 61 | 62 | /** 63 | * Inherit from `Store`. 64 | */ 65 | 66 | SessionStore.prototype.__proto__ = Store.prototype; 67 | 68 | /** 69 | * Auto clear expired data if the collection is not capped 70 | * 71 | * @param {Number} interval second 72 | * @api public 73 | * 74 | */ 75 | 76 | SessionStore.prototype.autoClear = function( interval ) { 77 | var self = this; 78 | setInterval(function() { 79 | !self.capped && self.collection.remove( { expires: {$lte: new Date()} } ); 80 | }, clear_interval * 1000); 81 | }; 82 | 83 | /** 84 | * Attempt to fetch session by the given `sid`. 85 | * 86 | * @param {String} sid 87 | * @param {Function} fn 88 | * @api public 89 | */ 90 | 91 | SessionStore.prototype.get = function(sid, fn) { 92 | var self = this; 93 | self.collection.findOne({_id: sid}) 94 | .done( function(sess) { 95 | if (sess) { 96 | if ( !sess.expires || new Date < sess.expires ) { 97 | //Make the session is string... we can't update undefined attr in capped collection... 98 | fn && fn( null, JSON.parse( sess.session ) ); 99 | } else { 100 | self.destroy( sid, fn ); 101 | } 102 | } else { 103 | fn && fn(); 104 | } 105 | } ) 106 | .fail( fn ); 107 | }; 108 | 109 | /** 110 | * Commit the given `sess` object associated with the given `sid`. 111 | * 112 | * @param {String} sid 113 | * @param {Session} sess 114 | * @param {Function} fn 115 | * @api public 116 | */ 117 | 118 | SessionStore.prototype.set = function( sid, session, fn ) { 119 | this.collection.update( 120 | { _id: sid } 121 | , { 122 | _id: sid 123 | , session: JSON.stringify( session ) 124 | , expires: session && session.cookie && session.cookie.expires ? 125 | new Date( session.cookie.expires ) : new Date("3040-1-1") 126 | } 127 | , { upsert: true, safe: true } 128 | , function( err ) { 129 | (fn || loop)( err ); 130 | } 131 | ); 132 | }; 133 | 134 | /** 135 | * Destroy the session associated with the given `sid`. 136 | * 137 | * @param {String} sid 138 | * @api public 139 | */ 140 | 141 | SessionStore.prototype.destroy = function( sid, fn ) { 142 | this.capped ? 143 | this.collection.update( {_id: sid}, { $set: { expires: new Date("2000-1-1") } }, {}, function( err ) { 144 | (fn || loop)( err ); 145 | } ) : 146 | this.collection.remove( {_id: sid}, function( err ) { 147 | (fn || loop)( err ); 148 | } ); 149 | }; 150 | 151 | /** 152 | * Fetch number of sessions. 153 | * 154 | * @param {Function} fn 155 | * @api public 156 | */ 157 | 158 | SessionStore.prototype.length = function( fn ) { 159 | this.collection.count({ expires: { $gt: new Date() } }, fn || loop ); 160 | }; 161 | 162 | /** 163 | * Clear all sessions. 164 | * 165 | * @param {Function} fn 166 | * @api public 167 | */ 168 | 169 | SessionStore.prototype.clear = function(fn) { 170 | this.collection.drop( fn || loop ); 171 | }; 172 | 173 | -------------------------------------------------------------------------------- /lib/collection.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Mongoq 3 | * 4 | * Copyright (c) 2011 Hidden 5 | * Released under the MIT, BSD, and GPL Licenses. 6 | * 7 | * Date: 2011-09-11 8 | */ 9 | 10 | /** 11 | * Depends 12 | */ 13 | 14 | var mongodb = require("mongodb") 15 | , util = require('./util') 16 | , EventEmitter = require("events").EventEmitter 17 | , inherits = require("util").inherits 18 | , cursor = require("./cursor") 19 | , slice = Array.prototype.slice 20 | , STATE_CLOSE = 0 21 | , STATE_OPENNING = 1 22 | , STATE_OPEN = 2; 23 | 24 | /** 25 | * Mongoq collection class 26 | * 27 | * @param {String} name 28 | * @param {Server} server 29 | * @param {Object} options 30 | * 31 | * @api public 32 | * 33 | */ 34 | 35 | module.exports = collection; 36 | function collection(name, db, options) { 37 | var self = this; 38 | EventEmitter.call(self); 39 | self.setMaxListeners( 0 ); //Disable max listener warning... 40 | self.name = name; 41 | self.db = db; 42 | self._options = options || {}; 43 | self.state = STATE_CLOSE; 44 | 45 | self.internalHint; 46 | self.__defineGetter__('hint', function() { return this.internalHint; }); 47 | self.__defineSetter__('hint', function(value) { 48 | this.internalHint = value; 49 | this.open(function(err, original) { 50 | if (original) { 51 | original.hint = value; 52 | self.internalHint = original.hint; 53 | } 54 | }); 55 | }); 56 | } 57 | 58 | /** 59 | * Enable events 60 | */ 61 | 62 | inherits( collection, EventEmitter ); 63 | 64 | 65 | /** 66 | * Inherits mongodb.Db methods 67 | * 68 | * insert (docs, options?, callback?) //=> this 69 | * checkCollectionName (collectionName) // Throw error is not valid 70 | * remove (selector?, options?, callback?) 71 | * rename (newName, callback) 72 | * insertAll (docs, options?, callback?) 73 | * save (doc, options?, callback?) //=> undefined 74 | * update (selector, document, options?, callback?) // options:upsert,multi,safe 75 | * distinct (key, query?, callback?) 76 | * count (query?, callback?) 77 | * drop (callback) 78 | * findAndModify (query, sort, doc, options?, callback?) // options: remove,unshift,new 79 | * find () //=> Cursor or undefined if callback 80 | * normalizeHintField (hint) // Get 81 | * findOne (queryObject, options?, callback) 82 | * createIndex (fieldOrSpec, options, callback?) 83 | * ensureIndex (fieldOrSpec, options, callback?) 84 | * indexInformation (options, callback) 85 | * dropIndex (name, callback) 86 | * dropIndexes (callback) 87 | * mapReduce (map, reduce, options, callback) 88 | * group (keys, condition, initial, reduce, command, callback) 89 | * options (callback) 90 | * 91 | */ 92 | 93 | var getters = [] 94 | , ignores = ["find", "drop", "normalizeHintField", "checkCollectionName"]; 95 | util.extend( collection, mongodb.Collection, getters, ignores, "original" ); 96 | 97 | 98 | /** 99 | * Open collction 100 | * 101 | * @param {Function} callback 102 | * @return {Collection} 103 | * @api public 104 | * 105 | */ 106 | 107 | collection.prototype.open = function( callback ) { 108 | var self = this; 109 | switch ( this.state ) { 110 | case STATE_OPEN: 111 | callback && callback.call(self, null, self.original); break; 112 | case STATE_OPENNING: 113 | callback && self.once('open', callback); break; 114 | case STATE_CLOSE: 115 | default: 116 | callback && self.once('open', callback); open(); 117 | } 118 | return this; 119 | function open() { 120 | self.state = STATE_OPENNING; 121 | self.db.open(function(err, original) { 122 | if (err) { 123 | return fail( err ); 124 | } 125 | self.db.original.createCollection(self.name, self._options, function(err, original) { 126 | err ? fail( err ) : done( original ); 127 | }); 128 | }); 129 | 130 | } 131 | function done (original) { 132 | self.state = STATE_OPEN; 133 | self.original = original; 134 | if( self.hint ) original.hint = self.hint; 135 | self.emit("open", null, self.original); 136 | } 137 | function fail (err) { 138 | self.state = STATE_CLOSE; 139 | self.emit("open", err, self.original); 140 | } 141 | } 142 | 143 | collection.prototype.close = function() { 144 | this.state = STATE_CLOSE; 145 | } 146 | 147 | collection.prototype.drop = function(callback) { 148 | this.db.dropCollection(this.name, callback); 149 | this.close(); 150 | return this; 151 | } 152 | 153 | 154 | /** 155 | * Find 156 | * 157 | * @return {Cursor} 158 | * @api public 159 | * 160 | */ 161 | 162 | collection.prototype.find = function() { 163 | return new cursor(null, this, slice.call(arguments, 0)); 164 | } 165 | 166 | /** 167 | * 168 | * Find items == find().toArray() 169 | * 170 | * @return {Collection} 171 | * @api public 172 | * 173 | */ 174 | 175 | collection.prototype.findItems = function() { 176 | var args = util.parseArgs( arguments ); 177 | this.find(args.clean).toArray(args.callback); 178 | return this; 179 | } 180 | -------------------------------------------------------------------------------- /test/db.test.js: -------------------------------------------------------------------------------- 1 | 2 | var mongoq = require('../index.js') 3 | , should = require('should'); 4 | 5 | describe("db", function() { 6 | 7 | it( "test db arguments", function(){ 8 | var db = mongoq("mongodb:\/\/fred:foobar@localhost:27017,localhost:27018/mongoqTest?reconnectWait=2000;retries=20"); 9 | var options = db.options; 10 | options.should.be.a('object'); 11 | options.username.should.eql("fred"); 12 | options.password.should.eql("foobar"); 13 | db.name.should.eql("mongoqTest"); 14 | 15 | var server = db.server 16 | , servers = server.servers; 17 | servers.should.have.length( 2 ); 18 | var server1 = servers[ 0 ] 19 | , server2 = servers[ 1 ]; 20 | server.reconnectWait.should.equal(2000); 21 | server.retries.should.equal(20); 22 | server1.host.should.eql("localhost"); 23 | server1.port.should.eql(27017); 24 | server1.autoReconnect.should.be.false; 25 | server1.poolSize.should.equal(1); 26 | 27 | server2.host.should.eql("localhost"); 28 | server2.port.should.eql(27018); 29 | 30 | db = mongoq("mongodb:\/\/127.0.0.1:27018/mongoqTest?auto_reconnect&poolSize=2"); 31 | options = db.options; 32 | options.should.be.a('object'); 33 | should.strictEqual(options.username, undefined); 34 | 35 | server = db.server; 36 | server.autoReconnect.should.be.true; 37 | server.poolSize.should.equal( 2 ); 38 | server.host.should.equal("127.0.0.1"); 39 | server.port.should.eql(27018); 40 | 41 | db = mongoq("mongodb:\/\/127.0.0.1:27018/mongoqTest?auto_reconnect=false"); 42 | options = db.options; 43 | options.should.be.a('object'); 44 | server = db.server; 45 | server.autoReconnect.should.be.false; 46 | 47 | db = mongoq("mongoqTest", {auto_reconnect: true}); 48 | options = db.options; 49 | options.should.be.a('object'); 50 | should.strictEqual(options.username, undefined); 51 | 52 | server = db.server; 53 | server.poolSize.should.equal( 1 ); 54 | 55 | server.host.should.equal("localhost"); 56 | server.port.should.eql(27017); 57 | server.autoReconnect.should.be.true; 58 | db.name.should.eql("mongoqTest"); 59 | 60 | db = mongoq("mongoqTest", {auto_reconnect: true, host: "127.0.0.1", port: "1233"}); 61 | server = db.server; 62 | server.host.should.equal("127.0.0.1"); 63 | server.port.should.eql(1233); 64 | }); 65 | 66 | it( "test db events[error,close,timeout]", function( done ) { 67 | var db = mongoq("mongoqTest") 68 | , db2 = mongoq("mongodb:\/\/127.0.0.1:27019/mongoqTest") 69 | , dbopen = false 70 | , db2open = false 71 | , close = false 72 | , error = false; 73 | 74 | db2.on("error", function() { 75 | error = true; 76 | }).on("close", function() { 77 | error = true; 78 | console.log( 111 ); 79 | }) 80 | .open(function(err) { 81 | db2open = true; 82 | err.should.be.an.instanceof(Error); 83 | 84 | db.on("close", function() { 85 | close = true; 86 | }) 87 | .open(function(err, originalDb) { 88 | dbopen = true; 89 | should.exist(originalDb); 90 | should.equal(err, null); 91 | 92 | should.strictEqual(dbopen, true); 93 | should.strictEqual(db2open, true); 94 | //should.strictEqual(error, true); 95 | 96 | db.close( done ); 97 | }); 98 | }); 99 | 100 | } ); 101 | 102 | it( "test db close", function(done) { 103 | var db = mongoq("mongoqTest") 104 | , hadOpen = false 105 | , hadClose = false; 106 | db.open(function(err, originalDb) { 107 | hadOpen = true; 108 | should.exist(originalDb); 109 | should.equal(err, null); 110 | 111 | }) 112 | .close(done); 113 | } ); 114 | 115 | it( "test db inherit methods", function(done) { 116 | var db = mongoq("mongoqTest") 117 | , hadOpen = false; 118 | db.dropCollection("test", function(err, success) { 119 | db.createCollection("test", function(err, collection) { 120 | should.equal(err, null); 121 | should.exist(collection); 122 | db.collectionNames(function(err, ar) { 123 | hadOpen = true; 124 | ar.should.be.instanceof( Array ); 125 | ar.length.should.be.above( 1 ); 126 | var hasCollectionTest = false; 127 | ar.forEach(function(col) { 128 | if( col.name == "mongoqTest.test" ) hasCollectionTest = true; 129 | }); 130 | hasCollectionTest.should.be.true; 131 | db.close(done); 132 | }); 133 | }); 134 | }); 135 | } ); 136 | it( "test db authenticate", function(done) { 137 | var db = mongoq("mongoqTest") 138 | , db2 = mongoq("mongodb:\/\/admin:foobar@localhost:27017/mongoqTest") 139 | , hadOpen = false; 140 | db.removeUser("admin", function(err, success) { 141 | db.addUser("admin", "foobar", function(err, user) { 142 | db.authenticate("admin", "foobar", function(err, success) { 143 | should.not.exist(err); 144 | success.should.be.true; 145 | db.close(); 146 | db2.options.username.should.eql("admin"); 147 | db2.options.password.should.eql("foobar"); 148 | db2.open(function(err, oDb) { 149 | hadOpen = true; 150 | db2.isAuthenticate.should.be.true; 151 | should.not.exist(err); 152 | db2.close( done ); 153 | }); 154 | }); 155 | }); 156 | }); 157 | }); 158 | }); 159 | -------------------------------------------------------------------------------- /test/collection.test.js: -------------------------------------------------------------------------------- 1 | 2 | var mongoq = require('../index.js') 3 | , should = require('should'); 4 | 5 | var colnum = 1; 6 | describe("collection", function() { 7 | it("test collection options", function(done) { 8 | var users = mongoq("mongoqTest", {auto_reconnect: true}).collection("users", {slaveOk: false}) 9 | , hadOpen = false; 10 | users.drop(function() { 11 | users.findOne(function() { 12 | var originalCol = users.original; 13 | originalCol.slaveOk.should.be.false; 14 | users.hint = {name: true}; 15 | should.exist( originalCol.hint ); 16 | originalCol.hint.name.should.be.true; 17 | users.db.close(done); 18 | }); 19 | }); 20 | }); 21 | it( "test inherit methods", function(done) { 22 | var users = mongoq("mongoqTest").collection("users" + (colnum++)) 23 | , hadOpen = false; 24 | users.drop(function() { 25 | users.insert({name: "Jack", phone: 1234567, email: "jack@mail.com"}, function() { 26 | users.findOne(function(err, user) { 27 | should.not.exist( err ); 28 | should.exist( user ); 29 | user.name.should.be.eql("Jack"); 30 | user.phone.should.be.equal(1234567); 31 | user._id.should.be.ok; 32 | hadOpen = true; 33 | users.db.close(done); 34 | }); 35 | }); 36 | }); 37 | }); 38 | it( "test find", function(done) { 39 | var db = mongoq("mongoqTest") 40 | , colname = "users" + (colnum++) 41 | , users = db.collection(colname) 42 | , hadOpen = false 43 | , hadOpen2 = false; 44 | users.drop(function() { 45 | users.insert({name: "Jack", phone: 1234567, email: "jack@mail.com"}, function() { 46 | db.close(function() { 47 | //Reconnect 48 | users.find(function(err, cursor) { //Callback 49 | should.not.exist( err ); 50 | should.exist( cursor ); 51 | }).toArray(function(err, docs) { 52 | hadOpen = true; 53 | should.exist( docs ); 54 | docs.should.be.an.instanceof( Array ); 55 | docs.should.have.length(1); 56 | var user = docs[0]; 57 | user.name.should.be.eql("Jack"); 58 | user.phone.should.be.equal(1234567); 59 | user._id.should.be.ok; 60 | users.find(function(err, cursor){ 61 | hadOpen2 = true; 62 | db.close(done); 63 | }); 64 | }); 65 | }); 66 | 67 | }); 68 | }); 69 | var num = 0; 70 | db.on("open", function() { 71 | num ++; 72 | }); 73 | }); 74 | it( "test findItems", function(done) { 75 | var users = mongoq("mongoqTest").collection("users" + (colnum++)) 76 | , hadOpen = false; 77 | users.drop(function() { 78 | users.insert({name: "Jack", phone: 1234567, email: "jack@mail.com"}, function() { 79 | users.findItems(function(err, docs) { //Callback 80 | hadOpen = true; 81 | should.exist( docs ); 82 | docs.should.be.an.instanceof( Array ); 83 | docs.should.have.length(1); 84 | var user = docs[0]; 85 | user.name.should.be.eql("Jack"); 86 | user.phone.should.be.equal(1234567); 87 | user._id.should.be.ok; 88 | users.db.close(done); 89 | }); 90 | }); 91 | }); 92 | }); 93 | it( "test cursor", function(done) { 94 | var users = mongoq("mongoqTest").collection("users" + (colnum++)) 95 | , hadOpen = false; 96 | users.drop(function() { 97 | users.insert([{name: "Jack", phone: 1234567, email: "jack@mail.com"}, {name: "Lucy", phone: 123, email: "lucy@mail.com"}], function() { 98 | users.find().skip(1).limit(1).toArray(function(err, docs) { //Callback 99 | hadOpen = true; 100 | should.exist( docs ); 101 | docs.should.be.an.instanceof( Array ); 102 | docs.should.have.length(1); 103 | var user = docs[0]; 104 | user.name.should.be.eql("Lucy"); 105 | user.phone.should.be.equal(123); 106 | user._id.should.be.ok; 107 | users.db.close(done); 108 | }); 109 | }); 110 | }); 111 | }); 112 | it( "test group", function(done) { 113 | var users = mongoq("mongoqTest").collection("users" + (colnum++)) 114 | , hadOpen = false; 115 | users.drop(function() { 116 | //Test count by group. 117 | users.insert([{name: "Jack", sex: "male", email: "jack@mail.com"}, {name: "Lucy", sex: "female", email: "lucy@mail.com"}, {name: "Lili", sex: "female", email: "lili@mail.com"}], function() { 118 | users.group({ "sex":true }, {}, {count:0}, function(obj, prev){ prev.count++ }, function(err, docs){ 119 | //dcos=> [ { sex: 'male', count: 1 }, { sex: 'female', count: 2 } ] 120 | should.exist( docs ); 121 | docs.should.be.an.instanceof( Array ); 122 | docs.should.have.length(2); 123 | users.group({ "sex":true }, {name: "Jack", email: "jack@mail.com"}, {count:0}, function(obj, prev){ prev.count++ }, function(err, docs){ 124 | should.exist( docs ); 125 | docs.should.be.an.instanceof( Array ); 126 | docs.should.have.length(1); 127 | docs[0].count.should.be.equal(1); 128 | docs[0].sex.should.be.equal("male"); 129 | hadOpen = true; 130 | users.db.close(done); 131 | }); 132 | }); 133 | }); 134 | }); 135 | }); 136 | 137 | it( "test insert push", function(done) { 138 | var users = mongoq("mongoqTest").collection("users" + (colnum++)) 139 | , hadOpen = false; 140 | users.drop(function() { 141 | users.insert({name: "jack", childrens: ["Lucy"]}, function() { 142 | users.update({name: "jack"}, {$push: {childrens: "Lili" }}, function() { 143 | users.findOne({name: "jack"}, function(err, user) { 144 | should.exist( user ); 145 | user.childrens.should.have.length( 2 ); 146 | user.childrens[1].should.equal("Lili"); 147 | users.db.close( done ); 148 | }); 149 | }); 150 | }); 151 | }); 152 | }); 153 | }); 154 | 155 | -------------------------------------------------------------------------------- /lib/db.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Mongoq 3 | * 4 | * Copyright (c) 2011 Hidden 5 | * Released under the MIT, BSD, and GPL Licenses. 6 | * 7 | * Date: 2011-09-11 8 | */ 9 | 10 | /** 11 | * Depends 12 | */ 13 | 14 | var mongodb = require("mongodb") 15 | , util = require('./util') 16 | , EventEmitter = require("events").EventEmitter 17 | , inherits = require("util").inherits 18 | , collection = require("./collection") 19 | , slice = Array.prototype.slice 20 | , STATE_CLOSE = 0 21 | , STATE_OPENNING = 1 22 | , STATE_OPEN = 2; 23 | 24 | /** 25 | * Mongoq db class 26 | * 27 | * @param {String} name 28 | * @param {Server} server 29 | * @param {Object} options 30 | * 31 | * @api public 32 | * 33 | */ 34 | 35 | module.exports = db; 36 | function db(name, server, options) { 37 | var self = this; 38 | EventEmitter.call(self); 39 | self.setMaxListeners( 0 ); //Disable max listener warning... 40 | self.name = name; 41 | self.server = server; 42 | self._collections = {}; 43 | self.options = options || {}; 44 | self.state = STATE_CLOSE; 45 | self.original = new mongodb.Db(this.name, this.server, this.options); 46 | //Fixed emission of `error` event resulting in an uncaught exception 47 | self.on('error', function(){}); 48 | // Inherits db events [error,close,timeout] 49 | ["error", "close", "timeout"].forEach(function(event) { 50 | self.original.on(event, function() { 51 | //Close all collections 52 | for( var key in self._collections ) { 53 | self._collections[key].close(); 54 | } 55 | //Close connect when error 56 | self.state = STATE_CLOSE; 57 | var args = slice.call(arguments, 0); 58 | args.unshift(event); 59 | self.emit.apply(self, args); 60 | }); 61 | }); 62 | 63 | 64 | } 65 | 66 | /** 67 | * Enable events 68 | */ 69 | 70 | inherits( db, EventEmitter ); 71 | 72 | 73 | /** 74 | * Inherits mongodb.Db methods 75 | * 76 | * open (callback) 77 | * close (callback) 78 | * admin (callback) 79 | * collectionsInfo (collectionName, callback) 80 | * collectionNames (collectionName, callback) 81 | * collection (collectionName, options, callback) 82 | * collections (callback) 83 | * eval (code, parameters, callback) 84 | * dereference (dbRef, callback) 85 | * logout (options, callback) 86 | * authenticate (username, password, callback) 87 | * addUser (username, password, callback) 88 | * removeUser (username, callback) 89 | * createCollection (collectionName, options, callback) 90 | * command (selector, callback) 91 | * dropCollection (collectionName, callback) 92 | * renameCollection (fromCollection, toCollection, callback) 93 | * lastError (options, connectionOptions, callback) 94 | * error (options, callback) 95 | * lastStatus (callback) 96 | * previousErrors (callback) 97 | * executeDbCommand (commandHash, options, callback) 98 | * executeDbAdminCommand (commandHash, callback) 99 | * resetErrorHistory (callback) 100 | * createIndex (collectionName, fieldOrSpec, options, callback) 101 | * ensureIndex (collectionName, fieldOrSpec, options, callback) 102 | * cursorInfo (callback) 103 | * dropIndex (collectionName, indexName, callback) 104 | * indexInformation (collectionName, options, callback) 105 | * dropDatabase (callback) 106 | * executeCommand (dbCommand, options, callback) 107 | * wrap 108 | * 109 | */ 110 | 111 | var getters = [] //admin 112 | , ignores = ["wrap", "collectionsInfo", "open", "close", "collection"]; 113 | 114 | util.extend( db, mongodb.Db, getters, ignores, "original" ); 115 | 116 | 117 | /** 118 | * Open database 119 | * 120 | * @param {Function} callback 121 | * @return {Db} 122 | * @api public 123 | * 124 | */ 125 | 126 | db.prototype.open = function( callback ) { 127 | var self = this; 128 | switch ( this.state ) { 129 | case STATE_OPEN: 130 | callback && callback.call(self, null, self.original); break; 131 | case STATE_OPENNING: 132 | callback && self.once('open', callback); break; 133 | case STATE_CLOSE: 134 | default: 135 | callback && self.once('open', callback); open(); 136 | } 137 | return this; 138 | function open() { 139 | self.state = STATE_OPENNING; 140 | self.original.open(function(err, original) { 141 | if( err ) { 142 | return fail(err); 143 | } 144 | if( self.options.username ) { 145 | self.original.authenticate(self.options.username, self.options.password, function(err, success){ 146 | //Sign for check 147 | self.isAuthenticate = true; 148 | err = success ? err: new Error('Could not authenticate user ' + self.options.username); 149 | err ? fail(err) : done(); 150 | }); 151 | } else { 152 | done(); 153 | } 154 | }); 155 | 156 | } 157 | function done () { 158 | self.state = STATE_OPEN; 159 | self.emit("open", null, self.original); 160 | } 161 | function fail (err) { 162 | self.state = STATE_CLOSE; 163 | self.emit("open", err, self.original); 164 | } 165 | } 166 | 167 | /** 168 | * Close database 169 | * 170 | * @param {Function} callback 171 | * @return {Db} 172 | * @api public 173 | * 174 | */ 175 | 176 | db.prototype.close = function(callback) { 177 | var self = this; 178 | //Close collections 179 | for( var key in self._collections ) { 180 | self._collections[key].close(); 181 | } 182 | switch ( this.state ) { 183 | case STATE_OPEN: 184 | self.state = STATE_CLOSE; self.original.close(callback); break; 185 | case STATE_OPENNING: 186 | closeWhenOpen(); break; 187 | case STATE_CLOSE: 188 | default: 189 | ("function" === typeof callback) && callback( null ); 190 | } 191 | return this; 192 | function closeWhenOpen (argument) { 193 | self.once("open", function(err, original) { 194 | self.state = STATE_CLOSE; 195 | if (err) { 196 | ("function" === typeof callback) && callback( null ); 197 | } 198 | else{ 199 | original.close( callback ); 200 | } 201 | }); 202 | } 203 | } 204 | 205 | /** 206 | * Find collection 207 | * 208 | * @param {String} name 209 | * @param {Object} options 210 | * @return {Collection} 211 | * @api public 212 | * 213 | */ 214 | 215 | 216 | db.prototype.collection = function( name, options ) { 217 | return this._collections[name] || ( this._collections[name] = new collection( name, this, options ) ); 218 | } 219 | 220 | 221 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | 2 | var jQuery = require("jquery-deferred") 3 | , slice = [].slice 4 | , isFunction = jQuery.isFunction; 5 | 6 | exports.parseArgs = parseArgs; 7 | exports.extend = extend; 8 | exports.and = and; 9 | exports.next = next; 10 | exports.when = jQuery.when; 11 | exports.Deferred = jQuery.Deferred; 12 | 13 | function parseArgs (args) { 14 | var all = slice.call(args, 0) 15 | , clean = slice.call(args, 0) 16 | , len = all.length - 1 17 | , callback = ( 'function' === typeof all[len] ) ? all[len] : null; 18 | 19 | if( callback ) clean.pop(); 20 | 21 | return { 22 | all: all, 23 | clean: clean, 24 | callback: callback 25 | }; 26 | } 27 | 28 | function extend(object, fromObject, getters, ignores, originalName, setters) { 29 | 30 | originalName = originalName || "original"; 31 | 32 | Object.keys(fromObject.prototype).forEach( function( key ) { 33 | 34 | if( ignores && ignores.indexOf( key ) != -1 ) return; 35 | 36 | if( getters && getters.indexOf( key ) != -1 ) { 37 | object.prototype[key] = function(){ 38 | var original = this[originalName]; 39 | return original && original[key].apply(original, arguments); 40 | } 41 | return; 42 | } 43 | 44 | if( setters && setters.indexOf( key ) != -1 ) { 45 | object.prototype[key] = function(){ 46 | var args = slice.call(arguments, 0); 47 | this.open(function(err, original) { 48 | if( !err ) { 49 | original[key].apply(original, args); 50 | } 51 | }); 52 | return this; 53 | } 54 | return; 55 | } 56 | 57 | object.prototype[key] = function(){ 58 | var self = this 59 | , args = parseArgs( arguments ) 60 | , callback = args.callback; 61 | 62 | // Deferred doesn't support find().each(), beacuse the callback will be called repeatedly 63 | var dfd = jQuery.Deferred(); 64 | 65 | self.open( function(err, original) { 66 | if( err ) { 67 | fn(err); 68 | } else { 69 | args.clean.push( fn ); 70 | original[key].apply(original, args.clean); 71 | } 72 | } ); 73 | 74 | function fn () { 75 | var args = slice.call(arguments, 0); 76 | callback && callback.apply( self, args ); 77 | if ( !args[0] ) { 78 | args.shift(); 79 | dfd.resolveWith( self, args ); 80 | } else { 81 | dfd.rejectWith( self, args ); 82 | } 83 | } 84 | var promise = dfd.promise(); 85 | promise.and = and; 86 | promise.next = next; 87 | return promise; 88 | } 89 | } ); 90 | } 91 | 92 | /** 93 | * and 94 | * 95 | * run promise object series or parallel and then serialize the result 96 | * 97 | * and( aPromise ) 98 | * .and( function( aValue ){ 99 | * return bPromise; 100 | * } ) 101 | * .and( cPromise ) 102 | * .done( function( aValue, bValue, cValue ){ 103 | * } ) 104 | * .fail( errorCallback ); 105 | * 106 | * @param {Function} firstParam first can be a Promise object 107 | * @return {Promise} 108 | * 109 | */ 110 | 111 | 112 | function and ( firstParam ) { 113 | 114 | var isThis = isFunction( this && this.promise ) 115 | , len = arguments.length; 116 | 117 | if( len > 1 ) { 118 | var args = slice.call( arguments, 0 ) 119 | , fn = args.shift() 120 | , promise = isThis ? this.and( fn ) : and( fn ); 121 | 122 | return promise.and.apply( promise, args ); 123 | } 124 | 125 | if( ! isThis ) { 126 | //The first deferred 127 | var promise = isFunction( firstParam ) ? firstParam() : firstParam; 128 | if( !isFunction( promise && promise.promise ) ) { 129 | promise = len ? jQuery.when( promise ) : jQuery.when(); 130 | } 131 | promise.and = and; 132 | promise.next = next; 133 | return promise; 134 | } 135 | 136 | var dfd = jQuery.Deferred(); 137 | this.then( function() { 138 | var last = slice.call( arguments ) 139 | , res; 140 | try { 141 | res = isFunction(firstParam) ? firstParam.apply( null, last ) : firstParam; 142 | } catch( e ) { 143 | res = e; 144 | } 145 | if( res instanceof Error ) { 146 | dfd.reject( res ); 147 | } else { 148 | res = isFunction( res && res.promise ) ? res : jQuery.when( res ); 149 | res.then( function( value ) { 150 | var args = [ arguments.length > 1 ? slice.call( arguments, 0 ) : value ] 151 | , i = last.length; 152 | while( i ) { 153 | args.unshift( last[ --i ] ); 154 | } 155 | dfd.resolve.apply( dfd, args ); 156 | }, dfd.reject ); 157 | } 158 | 159 | }, dfd.reject ); 160 | 161 | var promise = dfd.promise(); 162 | promise.and = and; 163 | promise.next = next; 164 | return promise; 165 | } 166 | 167 | /** 168 | * next 169 | * 170 | * run promise object series then give the result to the next 171 | * 172 | * next( aPromise ) 173 | * .next( function( aValue ){ 174 | * return bPromise; 175 | * } ) 176 | * .next( function( bValue ){ 177 | * return cPromise; 178 | * } ) 179 | * .done( function( cValue ){ 180 | * } ) 181 | * .fail( errorCallback ); 182 | * 183 | * @param {Function} firstParam first can be a Promise object 184 | * @return {Promise} 185 | * 186 | */ 187 | 188 | function next ( firstParam ) { 189 | 190 | var isThis = isFunction( this && this.promise ) 191 | , len = arguments.length; 192 | 193 | if( len > 1 ) { 194 | var args = slice.call( arguments, 0 ) 195 | , fn = args.shift() 196 | , promise = isThis ? this.next( fn ) : next( fn ); 197 | 198 | return promise.next.apply( promise, args ); 199 | } 200 | 201 | if( ! isThis ) { 202 | //The first deferred 203 | var promise = isFunction( firstParam ) ? firstParam() : firstParam; 204 | if( !isFunction( promise && promise.promise ) ) { 205 | promise = len ? jQuery.when( promise ) : jQuery.when(); 206 | } 207 | promise.next = next; 208 | promise.and = and; 209 | return promise; 210 | } 211 | 212 | var dfd = jQuery.Deferred(); 213 | this.then( function() { 214 | var last = slice.call( arguments ) 215 | , res; 216 | try { 217 | res = isFunction(firstParam) ? firstParam.apply( null, last ) : firstParam; 218 | } catch( e ) { 219 | res = e; 220 | } 221 | if( res instanceof Error ) { 222 | dfd.reject( res ); 223 | } else { 224 | res = isFunction( res && res.promise ) ? res : jQuery.when( res ); 225 | res.then( function( value ) { 226 | var args = [ arguments.length > 1 ? slice.call( arguments, 0 ) : value ]; 227 | dfd.resolve.apply( dfd, args ); 228 | }, dfd.reject ); 229 | } 230 | 231 | }, dfd.reject ); 232 | 233 | var promise = dfd.promise(); 234 | promise.next = next; 235 | promise.and = and; 236 | return promise; 237 | } 238 | 239 | 240 | -------------------------------------------------------------------------------- /test/util.test.js: -------------------------------------------------------------------------------- 1 | 2 | var mongoq = require('../index.js') 3 | , util = mongoq.util 4 | , should = require('should'); 5 | 6 | describe( "util", function() { 7 | it( 'test util parseArgs', function(){ 8 | (function() { 9 | var args = util.parseArgs(arguments); 10 | args.all.should.have.length(3); 11 | args.clean.should.have.length(3); 12 | args.clean[0].should.be.equal(1); 13 | should.not.exist(args.callback); 14 | 15 | })(1,2,3); 16 | 17 | (function() { 18 | var args = util.parseArgs(arguments); 19 | args.all.should.have.length(4); 20 | args.clean.should.have.length(3); 21 | args.clean[0].should.be.equal(1); 22 | should.exist(args.callback); 23 | args.callback.should.have.an.instanceof(Function); 24 | })(1,2,3, function() { 25 | }); 26 | }); 27 | 28 | describe( "and next", function() { 29 | var successPromise = function(val) { 30 | var dfd = util.Deferred(); 31 | var args = [].slice.call( arguments, 0 ); 32 | setTimeout( function() { 33 | dfd.resolve.apply( dfd, args ); 34 | } ,40 ); 35 | var p = dfd.promise(); 36 | p.and = util.and; 37 | p.next = util.next; 38 | return p; 39 | } 40 | , failPromise = function(val) { 41 | var dfd = util.Deferred(); 42 | setTimeout( function() { 43 | dfd.reject( val ); 44 | } ,40 ); 45 | var p = dfd.promise(); 46 | p.and = util.and; 47 | p.next = util.next; 48 | return p; 49 | }; 50 | 51 | it( "should success and", function( done ) { 52 | util.and( successPromise("v1") ) 53 | .and( function(v1) { 54 | return successPromise( "v2", "v21" ); 55 | }) 56 | .and( function(v1, v2) { 57 | return successPromise( ); 58 | }) 59 | .and( function(v1, v2, v3) { 60 | return successPromise( "v4", "v41" ); 61 | }) 62 | .done( function(v1, v2, v3, v4) { 63 | v1.should.be.eql("v1"); 64 | v2.should.be.eql(["v2", "v21"]); 65 | should.not.exist( v3 ); 66 | v4.should.be.eql(["v4", "v41"]); 67 | arguments.should.have.length( 4 ); 68 | done(); 69 | }) 70 | .fail(function() { 71 | false.should.be.true; 72 | }); 73 | } ); 74 | 75 | it( "should support anything value", function( done ) { 76 | util.and() 77 | .and( successPromise("v1") ) 78 | .and( function(v1) { 79 | return successPromise( "v2", "v21" ); 80 | }) 81 | .and( "v3" ) 82 | .done( function(v1, v2, v3) { 83 | v1.should.be.equal("v1"); 84 | v2.should.be.eql(["v2", "v21"]); 85 | v3.should.be.equal("v3"); 86 | arguments.should.have.length( 3 ); 87 | done(); 88 | }) 89 | .fail(function() { 90 | false.should.be.true; 91 | } ); 92 | } ); 93 | 94 | it( "should have and method", function( done ) { 95 | successPromise("v1") 96 | .and( function(v1) { 97 | return successPromise( "v2" ); 98 | }) 99 | .and( function(v1, v2) { 100 | return successPromise( "v3" ); 101 | }) 102 | .and( function(v1, v2, v3) { 103 | return successPromise( "v4" ); 104 | }) 105 | .done( function(v1, v2, v3, v4) { 106 | v1.should.be.equal("v1"); 107 | v2.should.be.equal("v2"); 108 | v3.should.be.equal("v3"); 109 | v4.should.be.equal("v4"); 110 | arguments.should.have.length( 4 ); 111 | done(); 112 | }) 113 | .fail(function() { 114 | false.should.be.true; 115 | } ); 116 | } ); 117 | 118 | it( "should support failed message", function( done ) { 119 | successPromise("v1") 120 | .and( function(v1) { 121 | return successPromise( "v2" ); 122 | }) 123 | .and( function(v1, v2) { 124 | return failPromise( "e3" ); 125 | }) 126 | .done( function(v1, v2, v3) { 127 | false.should.be.true; 128 | }) 129 | .fail( function( err ) { 130 | err.should.be.equal("e3"); 131 | done(); 132 | }); 133 | } ); 134 | 135 | it( "should support failed message return", function( done ) { 136 | successPromise("v1") 137 | .and( function(v1) { 138 | return successPromise( "v2" ); 139 | }) 140 | .and( function(v1, v2) { 141 | return new Error( "e3" ); 142 | }) 143 | .done( function(v1, v2, v3) { 144 | false.should.be.true; 145 | }) 146 | .fail( function( err ) { 147 | err.message.should.be.equal("e3"); 148 | done(); 149 | }); 150 | } ); 151 | 152 | it( "should support throw error", function( done ) { 153 | successPromise("v1") 154 | .and( function(v1) { 155 | return successPromise( "v2" ); 156 | }) 157 | .and( function(v1, v2) { 158 | throw new Error("e3"); 159 | }) 160 | .done( function(v1, v2, v3) { 161 | false.should.be.true; 162 | }) 163 | .fail( function( err ) { 164 | err.message.should.be.equal("e3"); 165 | done(); 166 | }); 167 | } ); 168 | 169 | it( "should multi arguments", function( done ) { 170 | util.and( 171 | successPromise("v1") 172 | , function(v1) { 173 | return successPromise( "v2", "v21" ); 174 | } 175 | , function(v1, v2) { 176 | return successPromise( ); 177 | } 178 | , function(v1, v2, v3) { 179 | return successPromise( "v4", "v41" ); 180 | } 181 | ) 182 | .done( function(v1, v2, v3, v4) { 183 | v1.should.be.eql("v1"); 184 | v2.should.be.eql(["v2", "v21"]); 185 | should.not.exist( v3 ); 186 | v4.should.be.eql(["v4", "v41"]); 187 | arguments.should.have.length( 4 ); 188 | done(); 189 | }) 190 | .fail(function() { 191 | false.should.be.true; 192 | }); 193 | } ); 194 | 195 | it( "should success next", function( done ) { 196 | util.next( successPromise("v1") ) 197 | .next( function(v1) { 198 | v1.should.be.eql("v1"); 199 | return successPromise( "v2", "v21" ); 200 | }) 201 | .next( function(v2) { 202 | v2.should.be.eql(["v2", "v21"]); 203 | return successPromise( ); 204 | }) 205 | .next( function(v3) { 206 | should.not.exist( v3 ); 207 | return successPromise( "v4", "v41" ); 208 | }) 209 | .done( function(v4) { 210 | v4.should.be.eql(["v4", "v41"]); 211 | arguments.should.have.length( 1 ); 212 | done(); 213 | }) 214 | .fail(function() { 215 | false.should.be.true; 216 | }); 217 | } ); 218 | 219 | it( "should support throw error with next", function( done ) { 220 | successPromise("v1") 221 | .next( function(v1) { 222 | v1.should.be.eql("v1"); 223 | return successPromise( "v2" ); 224 | }) 225 | .next( function(v2) { 226 | v2.should.be.eql("v2"); 227 | throw new Error("e3"); 228 | }) 229 | .done( function(v3) { 230 | false.should.be.true; 231 | }) 232 | .fail( function( err ) { 233 | err.message.should.be.equal("e3"); 234 | done(); 235 | }); 236 | } ); 237 | 238 | it( "should work together", function( done ) { 239 | successPromise("v1") 240 | .next( function(v1) { 241 | v1.should.be.eql("v1"); 242 | return v1 + "v1"; 243 | } ) 244 | .and( successPromise("v2") ) 245 | .next( function(v1, v2) { 246 | v1.should.be.eql("v1v1"); 247 | v2.should.be.eql("v2"); 248 | return successPromise(v1 + v2) 249 | .and( successPromise( "v3" ) ); 250 | } ) 251 | .and( "v4" ) 252 | .done( function(v3, v4) { 253 | v3.should.be.eql([ "v1v1v2", "v3" ]); 254 | v4.should.be.eql( "v4" ); 255 | done(); 256 | }) 257 | .fail( function() { 258 | false.should.be.true; 259 | }); 260 | } ); 261 | 262 | 263 | } ); 264 | 265 | }); 266 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | MongoQ 3 | ============================ 4 | 5 | Use mongoDB like this: 6 | 7 | `mongoq("testdb").collection("users").find().toArray().done( function(docs){} ).fail( function(err){} )`; 8 | 9 | Base on [node-mongodb-native][mongodb-native] 10 | 11 | 12 | Features 13 | ----------------------------- 14 | 15 | * Standard [connection string format][connection string] 16 | * Full [node-mongodb-native][mongodb-native] methods supports 17 | * Chainable api 18 | * Introduce into [jQuery Deferred][jquery-deferred] which is based on the [CommonJS Promises/A][promises-a] design. => v0.2 19 | * Control-flow => v0.2 20 | 21 | 22 | Installation 23 | ----------------------------- 24 | 25 | > npm install mongoq 26 | 27 | Example 28 | ----------------------------- 29 | 30 | > var assert = require("assert") 31 | > , mongoq = require("../index.js") 32 | > , db = mongoq("mongodb://127.0.0.1:27017/mongoqTest") 33 | > , User = db.collection("users"); 34 | > 35 | > User.remove() //clear test date 36 | > .done( function() { 37 | > User.insert( [{ _id: 1, name: "Jack", age: 22 }, { _id: 2, name: "Lucy", age: 20 }] ) //Add Jack and Lucy 38 | > .and( User.insert( { _id: 3, name: "Mike", age: 21 } ) ) //Add Mike synchronous 39 | > .and( function(u1, u2) { 40 | > // Will find after add Jack, Lucy and Mike 41 | > return User.findOne( { name: u2[0]["name"] } ) 42 | > } ) 43 | > .done( function(u1, u2, u3) { //All be done 44 | > assert.deepEqual( u1, [{ _id: 1, name: "Jack", age: 22 }, { _id: 2, name: "Lucy", age: 20 }], "Insert first" ); 45 | > assert.deepEqual( u2, [{ _id: 3, name: "Mike", age: 21 }], "insert second" ); 46 | > assert.deepEqual( u3, { _id: 3, name: "Mike", age: 21 }, "Find after insert" ); 47 | > db.close(); 48 | > } ) 49 | > .fail( function( err ) { // Any error occur 50 | > console.log( err ); 51 | > } ); 52 | > } ) 53 | > .fail( function( err ) { // Faild to remove 54 | > console.log( err ); 55 | > } ); 56 | > 57 | 58 | Work like node-mongodb-native 59 | ----------------------------------------- 60 | 61 | Mongoq bridge all the methods and events from [mongodb native database][mongodb-native-database] and [mongodb native collections][mongodb-native-collections], and make it chainable. 62 | 63 | ###Access BSON 64 | 65 | > var mongoq = require("mongoq"); 66 | > 67 | > var BSON = mongoq.BSON; 68 | > var ObjectID = BSON.ObjectID; 69 | > 70 | 71 | ###Database 72 | 73 | Provide a simple [connection string][connection string] 74 | 75 | > var mongoq = require("mongoq"); 76 | > 77 | > //use default server localhost:27017, poolSize 1 78 | > var db = mongoq("testdb"); 79 | > 80 | > //use options 81 | > db = mongoq("testdb", {host: "127.0.0.1", port: "27017"}); 82 | > 83 | > //connection string 84 | > db = mongoq("mongodb://localhost/testdb"); 85 | > 86 | > // Connect and login to the "testdb" database as user "admin" with passowrd "foobar" 87 | > db = mongoq("mongodb://admin:foobar@localhost:27017/testdb?poolSize=2"); 88 | > 89 | > //Repl set servers 90 | > db = mongoq("mongodb://admin:foobar@localhost:27017,localhost:27018/testdb?reconnectWait=2000;retries=20"); 91 | > 92 | > //Add user 93 | > db.addUser("admin", "foobar", function(err) {}); 94 | 95 | methods 96 | 97 | * close(callback) 98 | * admin(callback) 99 | * collectionNames(collectionName?, callback) 100 | * collection(collectionName, options?, callback) 101 | * collections(callback) 102 | * dereference(dbRef, callback) 103 | * logout(options, callback) Logout user from server, Fire off on all connections and remove all auth info 104 | * authenticate(username, password, callback) 105 | * addUser(username, password, callback) 106 | * removeUser(username, callback) 107 | * createCollection(collectionName, options?, callback) 108 | * dropCollection(collectionName, callback) 109 | * renameCollection(fromCollection, toCollection, callback) 110 | * lastError(options, connectionOptions, callback) 111 | * error(options, callback) 112 | * lastStatus(callback) 113 | * previousErrors(callback) 114 | * executeDbCommand(commandHash, options?, callback) 115 | * executeDbAdminCommand(commandHash, callback) 116 | * resetErrorHistory(callback) 117 | * createIndex(collectionName, fieldOrSpec, options?, callback) Create an index on a collection 118 | * ensureIndex(collectionName, fieldOrSpec, options?, callback) Ensure index, create an index if it does not exist 119 | * dropIndex(collectionName, indexName, callback) Drop Index on a collection 120 | * indexInformation(collectionName, options..., callback) 121 | * dropDatabase(callback) 122 | * cursorInfo(callback) Fetch the cursor information 123 | * executeCommand(dbCommand, options, callback) 124 | 125 | 126 | ###Collection 127 | 128 | > var mongoq = require("mongoq"); 129 | > var db = mongoq("mongodb://localhost/testdb"); 130 | > var users = db.collection("users"); 131 | > users.insert({name: "Jack", phone: 1234567, email: "jake@mail.com"}); 132 | 133 | methods 134 | 135 | * insert (docs, options?, callback?) 136 | * remove (selector?, options?, callback?) 137 | * rename (newName, callback) 138 | * insertAll (docs, options?, callback?) 139 | * save (doc, options?, callback?) 140 | * update (selector, document, options?, callback?) // options:upsert,multi,safe 141 | * distinct (key, query?, callback?) 142 | * count (query?, callback) 143 | * drop (callback) 144 | * findAndModify (query, sort, doc, options?, callback?) // options: remove,unshift,new 145 | * find () //return Cursor 146 | * findOne (queryObject, options?, callback) 147 | * createIndex (fieldOrSpec, options, callback?) 148 | * ensureIndex (fieldOrSpec, options, callback?) 149 | * indexInformation (options, callback) 150 | * dropIndex (name, callback) 151 | * dropIndexes (callback) 152 | * mapReduce (map, reduce, options, callback) 153 | * group (keys, condition, initial, reduce, command, callback) 154 | * options (callback) 155 | 156 | 157 | ###Cursor 158 | 159 | > var mongoq = require("mongoq"); 160 | > var db = mongoq("mongodb://localhost/testdb"); 161 | > var users = db.collection("users"); 162 | > var cursor = users.find(); 163 | > cursor.toArray(function(err, users){ 164 | > db.close(); 165 | > }); 166 | 167 | 168 | methods 169 | 170 | * toArray(callback) 171 | * each(callback) 172 | * count(callback) 173 | * sort(keyOrList, direction) //=> this 174 | * limit(limit) //=> this 175 | * skip(limit) //=> this 176 | * batchSize(limit) //=> this 177 | * nextObject(callback) 178 | * getMore(callback) 179 | * explain(callback) 180 | 181 | 182 | 183 | MongoQ style 184 | ----------------------------- 185 | 186 | ###Deferred Object 187 | 188 | MongoQ introduce into jQuery Deferred since v0.2, you can find more documents about jQuery Deferred Object at [here][jquery-deferred]. 189 | 190 | MongoQ make all mongodb asynchronous processes to return with a Promise Object. 191 | 192 | > var mongoq = require("mongoq"); 193 | > var db = mongoq("mongodb://localhost/testdb"); 194 | > var users = db.collection("users"); 195 | > users.find().toArray() 196 | > .done( function( docs ) { 197 | > //=> users 198 | > } ) 199 | > .done( function( docs ) { 200 | > //=> users 201 | > } ) 202 | > .fail( function( error ) { 203 | > //=> error 204 | > } ) 205 | > .then( function( docs ) { 206 | > //=> users 207 | > }, function( error ) { 208 | > //=> error 209 | > } ); 210 | 211 | methods 212 | 213 | * done( doneCallbacks [, doneCallbacks] ) //=> Add handlers to be called when the Deferred object is resolved. 214 | * fail( failCallbacks [, failCallbacks] ) //=> Add handlers to be called when the Deferred object is rejected. 215 | * then( doneCallbacks, failCallbacks ) //=> Add handlers to be called when the Deferred object is resolved or rejected. 216 | * always( alwaysCallbacks ) //=> Add handlers to be called when the Deferred object is either resolved or rejected. 217 | 218 | 219 | **Notice**: Please don't use `find().each().done(...`, the callbacks will be called only once. 220 | 221 | 222 | ###Control-flow 223 | 224 | MongoQ add two methods called `and` and `next` to the Promise Object for the mongodb's parallel execution, serial execution and error handling painless. 225 | 226 | **and**: run promise object series or parallel and then serialize the result 227 | 228 | > var mongoq = require("mongoq"); 229 | > var db = mongoq("mongodb://localhost/testdb"); 230 | > var users = db.collection("users"); 231 | > var messages = db.collection("messages"); 232 | > users.count() 233 | > .and( users.findOne() ) // parallel 234 | > .and( function( user ) { // serial when in function 235 | > return user ? messages.find({ user: user._id }).toArray() : []; 236 | > } ) 237 | > .done( function( num, user, msgs ) { 238 | > //num from users.count 239 | > //user from users.findOne 240 | > //msgs from messages.find 241 | > } ) 242 | > .fail( function( err ) {} ); 243 | 244 | **next**: run promise object series then give the result to the next 245 | 246 | > var mongoq = require("mongoq"); 247 | > var db = mongoq("mongodb://localhost/testdb"); 248 | > var users = db.collection("users"); 249 | > var messages = db.collection("messages"); 250 | > users.findOne() 251 | > .next( function( user ) { // serial when in function 252 | > return user ? messages.find({ user: user._id }).toArray() : []; 253 | > } ) 254 | > .done( function( msgs ) { 255 | > //msgs from messages.find 256 | > } ) 257 | > .fail( function( err ) {} ); 258 | 259 | 260 | Contributor 261 | ----------------------------- 262 | 263 | * Caio Ribeiro Pereira (caio.ribeiro.pereira@gmail.com) 264 | 265 | 266 | License 267 | ----------------------------- 268 | 269 | (The MIT License) 270 | 271 | Copyright (c) 2011 hidden <zzdhidden@gmail.com> 272 | 273 | Permission is hereby granted, free of charge, to any person obtaining 274 | a copy of this software and associated documentation files (the 275 | 'Software'), to deal in the Software without restriction, including 276 | without limitation the rights to use, copy, modify, merge, publish, 277 | distribute, sublicense, and/or sell copies of the Software, and to 278 | permit persons to whom the Software is furnished to do so, subject to 279 | the following conditions: 280 | 281 | The above copyright notice and this permission notice shall be 282 | included in all copies or substantial portions of the Software. 283 | 284 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 285 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 286 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 287 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 288 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 289 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 290 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 291 | 292 | 293 | [mongodb-native]: https://github.com/christkv/node-mongodb-native 294 | [mongodb-native-database]: https://github.com/christkv/node-mongodb-native/blob/master/docs/database.md 295 | [mongodb-native-collections]: https://github.com/christkv/node-mongodb-native/blob/master/docs/collections.md 296 | [promises-a]: http://wiki.commonjs.org/wiki/Promises/A 297 | [jquery-deferred]: http://api.jquery.com/category/deferred-object/ 298 | [connection-string]: http://www.mongodb.org/display/DOCS/Connections 299 | --------------------------------------------------------------------------------