Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

first commit

  • Loading branch information...
commit cd524ad7223e1ac0e162fcd6c66581e621978765 0 parents
Gui Lin authored
204 Readme.md
@@ -0,0 +1,204 @@
+
+Install
+========
+
+Soon, not publish to npm yet.
+
+Introduction
+========
+**Mongoskin** is the future layer above [node-mongodb-native](https://github.com/christkv/node-mongodb-native)
+
+ var mongo = require('mongoskin');
+ mongo.db('localhost/test-database').collection('articls').insert({a:1}, function(err, replies){
+ console.dir(replies);
+ });
+
+Is mongoskin synchronized?
+========
+
+Nop! It is [future](http://en.wikipedia.org/wiki/Future_(programming))
+
+Goals
+========
+
+Provide full features of [node-mongodb-native](https://github.com/christkv/node-mongodb-native),
+and make it [future](http://en.wikipedia.org/wiki/Future_(programming)).
+
+Documentation
+========
+* [Module API](#module-api)
+* [SkinServer](#skinserver)
+* [SkinDb](#skindb)
+* [SkinCollection](#skincollection)
+
+for more information, see the source.
+
+Module API
+--------
+
+### MongoSkin Url format
+
+ [*://][username:password@]host[:port][/database][?auto_reconnect[=true|false]]`
+
+e.g.
+
+ localhost/blog
+ mongo://admin:pass@127.0.0.1:27017/blog?auto_reconnect
+ 127.0.0.1?auto_reconnect=false
+
+
+### bind(collectionName)
+
+### bind(collectionName, SkinCollection)
+
+### bind(collectionName, extendObject1, extendObject2 ...)
+
+Bind SkinCollection to db properties. see [SkinDb.bind](#bindcollectionname) for more information.
+
+### db(databaseUrl)
+
+Get or create instance of SkinDb.
+
+### cluster(serverUrl1, serverUrl2, ...)
+
+Create SkinServer of native ServerCluster. e.g.
+
+ var mongo = require('mongoskin');
+ var cluster = mongo.cluster('192.168.0.1:27017', '192.168.0.2:27017', '192.168.0.3:27017')
+ var db = cluster.db('dbname', 'admin', 'pass');
+
+### pair(leftServerUrl, rightServerUrl)
+
+Create instance of native ServerPair
+
+
+SkinServer
+--------
+
+### SkinServer(server)
+
+Construct SkinServer from native Server instance.
+
+### db(dbname, username=null, password=null)
+
+Construct SkinDb from SkinServer.
+
+
+SkinDb
+--------
+
+### SkinDb(db, username=null, password=null)
+
+Construct SkinDb.
+
+### open(callback)
+
+Connect to database, retrieval native Db instance, callback is function(err, db).
+
+### collection(collectionName)
+
+Retrieval SkinCollection instance of specified collection name.
+
+### bind(collectionName)
+
+### bind(collectionName, SkinCollection)
+
+### bind(collectionName, extendObject1, extendObject2 ...)
+
+Bind SkinCollection to db properties as a shortcut to db.collection(name).
+You can also bind additional methods to the SkinCollection, it is useful when
+you want to reuse a complex operation. This will also affect
+db.collection(name) method.
+
+e.g.
+
+ db.bind('book', {
+ firstBook: function(fn){
+ this.findOne(fn);
+ }
+ });
+ db.book.firstBook:(function(err, book){});
+
+### all the methods from Db.prototype
+
+See [node-mongodb-native](https://github.com/christkv/node-mongodb-native) for more information.
+
+SkinCollection
+--------
+
+### open(callback)
+
+Retrieval native Collection instance, callback is function(err, collection).
+
+### id(hex)
+
+Equivalent to
+
+ db.bson_serilizer.ObjectID.createFromHexString(hex);
+
+### findItems(..., callback)
+
+Equivalent to
+
+ collection.find(..., function(err, cursor){
+ cursor.toArray(callback);
+ });
+
+### findEach(..., callback)
+
+Equivalent to
+
+ collection.find(..., function(err, cursor){
+ cursor.each(callback);
+ });
+
+### findById(id, ..., callback)
+
+Equivalent to
+
+ collection.findOne({_id, ObjectID.createFromHexString(id)}, ..., callback);
+
+### updateById(_id, ..., callback)
+
+Equivalent to
+
+ collection.update({_id, ObjectID.createFromHexString(id)}, ..., callback);
+
+### find(...)
+
+If the last parameter is function, it is equivalent to native Collection.find
+method, else it will return a future SkinCursor.
+
+e.g.
+
+ // callback
+ db.book.find({}, function(err, cursor){/* do something */});
+ // future SkinCursor
+ db.book.find().toArray(function(err, books){/* do something */});
+
+### all the methods from Collection.prototype
+
+See [node-mongodb-native](https://github.com/christkv/node-mongodb-native) for more information.
+
+ checkCollectionName
+ count
+ createIndex
+ distinct
+ drop
+ dropIndex
+ dropIndexes
+ ensureIndex
+ find
+ findAndModify
+ findOne
+ group
+ indexInformation
+ insert
+ insertAll
+ mapReduce
+ normalizeHintField
+ options
+ remove
+ rename
+ save
+ update
1  index.js
@@ -0,0 +1 @@
+module.exports = require('./lib/mongoskin');
120 integration/integration_tests.js
@@ -0,0 +1,120 @@
+GLOBAL.DEBUG = true;
+
+var assert = require('assert'),
+ mongo = require('../lib/mongoskin');
+
+console.log('======== test MongoSkin.db ========');
+['localhost/test', 'db://admin:admin@localhost:27017/test?auto_reconnect']
+.forEach(function(server) {
+ db = mongo.db(server);
+ db.open(function(err, db) {
+ assert.ok(db, 'fail to open ' + server);
+ });
+});
+
+var bindToBlog = {
+ first: function(fn) {
+ this.findOne(fn);
+ }
+};
+
+console.log('======== test MongoSkin.bind ========');
+mongo.bind('blog', bindToBlog);
+mongo.bind('users');
+var db = mongo.db('localhost/test_mongoskin');
+assert.equal(db.blog.first, bindToBlog.first);
+assert.ok(db.users);
+
+console.log('======== test SkinDb.bind ========');
+db.bind('blog2', bindToBlog);
+db.bind('user2');
+assert.equal(db.blog2.first, bindToBlog.first);
+assert.ok(db.user2);
+
+console.log('======== test SkinDb.open ========');
+var db1, db2;
+db.open(function(err, db) {
+ assert.ok(db, err && err.stack);
+ db1 = db;
+ assert.equal(db1.state, 'connected');
+ if (db2) {
+ assert.equal(db1, db2, 'should alwayse be the same instance in db.open.');
+ }
+});
+
+db.open(function(err, db) {
+ assert.ok(db, err && err.stack);
+ db2 = db;
+ assert.equal(db2.state, 'connected');
+ if (db1) {
+ assert.equal(db1, db2, 'should alwayse be the same instance in db.open.');
+ }
+});
+
+console.log('======== test normal method of SkinDb ========');
+db.createCollection('test_createCollection', function(err, collection) {
+ assert.equal(db.db.state, 'connected');
+ assert.ok(collection, err && err.stack);
+});
+
+
+console.log('======== test SkinDb.collection ========');
+assert.equal(db.blog, db.collection('blog'));
+
+console.log('======== test SkinCollection.open ========');
+var coll1, coll2;
+db.blog.open(function(err, coll) {
+ assert.ok(coll, err && err.stack);
+ coll1 = coll;
+ if (coll2) {
+ assert.equal(coll1, coll2, 'should be the same instance in collection.open');
+ }
+});
+
+db.blog.open(function(err, coll) {
+ assert.ok(coll, err && err.stack);
+ coll2 = coll;
+ if (coll1) {
+ assert.equal(coll1, coll2, 'should be the same instance in collection.open');
+ }
+});
+
+console.log('======== test normal method of SkinCollection ========');
+db.collection('test_normal').ensureIndex([['a',1]], function(err, replies){
+ assert.ok(replies, err && err.stack);
+});
+
+console.log('======== test SkinCollection.find ========');
+collection = db.collection('test_find');
+collection.insert([{a:1},{a:2},{a:3}], function(err, replies){
+ assert.ok(replies, err && err.stack);
+ collection.findItems(function(err, items){
+ assert.ok(items, err && err.stack);
+ console.log('found '+ items.length + ' items');
+ });
+ collection.findEach(function(err, item){
+ assert.ok(!err, err && err.stack);
+ });
+ collection.find(function(err, cursor){
+ assert.ok(cursor, err && err.stack);
+ });
+
+ console.log('======== test SkinCursor ========');
+ collection.find().toArray(function(err, items){
+ console.log('======== test find cursor toArray========');
+ assert.ok(items, err && err.stack);
+ console.dir(items);
+ });
+ collection.find().each(function(err, item){
+ console.log('======== test find cursor each========');
+ assert.ok(!err, err && err.stack);
+ console.dir(item);
+ });
+});
+
+/*
+console.log('======== test SkinDb.close ========');
+db.close();
+assert.equal(db.db.state, 'notConnected');
+*/
+
26 lib/mongoskin/admin.js
@@ -0,0 +1,26 @@
+var Admin = require('mongodb').Admin;
+
+var SkinAdmin = exports.SkinAdmin = function(db) {
+ this.db = db;
+ this.nativeAdmin = new Admin(this.db.nativeDb);
+}
+
+var bindSkin = function(name, method) {
+ SkinAdmin.prototype[name] = function() {
+ var args = arguments.length > 0 ? Array.prototype.slice.call(arguments, 0) : [];
+ return this.db.open(function(err, db) {
+ if (err) {
+ args[args.length - 1](err);
+ } else {
+ method.apply(this.nativeAdmin, args);
+ }
+ });
+ };
+};
+
+for (var name in Admin.prototype) {
+ var method = Admin.prototype[name];
+ bindSkin(name, method);
+}
+
+exports.SkinAdmin = SkinAdmin;
170 lib/mongoskin/collection.js
@@ -0,0 +1,170 @@
+/**
+ bind these methods from Collection.prototype to Provider
+
+ methods:
+ insert
+ checkCollectionName
+ remove
+ rename
+ insertAll
+ save
+ update
+ distinct
+ count
+ drop
+ findAndModify
+ find
+ normalizeHintField
+ findOne
+ createIndex
+ ensureIndex
+ indexInformation
+ dropIndex
+ dropIndexes
+ mapReduce
+ group
+ options
+*/
+var __slice = Array.prototype.slice,
+ events = require('events'),
+ Collection = require('mongodb').Collection,
+ SkinCursor = require('./cursor').SkinCursor,
+ STATE_CLOSE = 0,
+ STATE_OPENNING = 1,
+ STATE_OPEN = 2;
+
+var SkinCollection = exports.SkinCollection = function(skinDb, collectionName) {
+ this.skinDb = skinDb;
+ this.collectionName = collectionName;
+ this.collection;
+ this.state = STATE_CLOSE;
+ this.internalHint;
+ var that = this;
+ this.__defineGetter__('hint', function() { return this.internalHint; });
+ this.__defineSetter__('hint', function(value) {
+ this.internalHint = value;
+ this.open(function(err, collection) {
+ collection.hint = value;
+ that.internalHint = collection.hint;
+ });
+ });
+
+ this.emitter = new events.EventEmitter();
+}
+
+var bindSkin = function(name, method) {
+ SkinCollection.prototype[name] = function() {
+ var args = arguments.length > 0 ? __slice.call(arguments, 0) : [];
+ return this.open(function(err, collection) {
+ if (err) {
+ args[args.length - 1](err);
+ } else {
+ method.apply(collection, args);
+ }
+ });
+ };
+};
+
+for (var name in Collection.prototype) {
+ var method = Collection.prototype[name];
+ bindSkin(name, method);
+}
+
+SkinCollection.prototype._find = SkinCollection.prototype.find;
+
+SkinCollection.prototype.open = function(fn) {
+ switch (this.state) {
+ case STATE_OPEN:
+ return fn(null, this.collection);
+ case STATE_OPENNING:
+ return this.emitter.addListener('open', fn);
+ case STATE_CLOSE:
+ default:
+ var that = this;
+ this.emitter.addListener('open', fn);
+ this.state = STATE_OPENNING;
+ this.skinDb.open(function(err, db) {
+ if (err) {
+ that.state = STATE_CLOSE;
+ return that.emitter.emit('open', err, collection);
+ }
+ that.skinDb.db.collection(that.collectionName, function(err, collection) {
+ if (collection) {
+ that.state = STATE_OPEN;
+ that.collection = collection;
+ if (that.hint) {
+ that.collection.hint = that.hit;
+ }
+ }else {
+ that.state = STATE_CLOSE;
+ }
+ that.emitter.emit('open', err, collection);
+ });
+ });
+ }
+};
+
+SkinCollection.prototype.close = function(){
+ this.state = STATE_CLOSE;
+}
+
+SkinCollection.prototype.drop = function(fn) {
+ this.skinDb.dropCollection(this.collectionName, fn);
+ this.close();
+};
+
+SkinCollection.prototype.findItems = function() {
+ var args = __slice.call(arguments),
+ fn = args[args.length - 1];
+
+ args[args.length - 1] = function(err, cursor) {
+ if (err) {
+ fn(err);
+ } else {
+ cursor.toArray(fn);
+ }
+ }
+
+ this._find.apply(this, args);
+};
+
+SkinCollection.prototype.findEach = function() {
+ var args = __slice.call(arguments),
+ fn = args[args.length - 1];
+
+ args[args.length - 1] = function(err, cursor) {
+ if (err) {
+ fn(err);
+ } else {
+ cursor.each(fn);
+ }
+ }
+
+ this._find.apply(this, args);
+};
+
+SkinCollection.prototype.id = function(hex) {
+ this.skinDb.db.bson_serializer.ObjectID.createFromHexString(hex);
+};
+
+SkinCollection.prototype.findById = function() {
+ var args = __slice.call(arguments);
+ args[0] = {_id: this.id(args[0])};
+ this.findOne.apply(this, args);
+};
+
+SkinCollection.prototype.updateById = function() {
+ var args = __slice.call(arguments);
+ args[0] = {_id: this.id(args[0])};
+ this.update.apply(this, args);
+};
+
+SkinCollection.prototype.find = function() {
+ var args = arguments.length > 0 ? __slice.call(arguments, 0) : [];
+ if (args.length > 0 && typeof(args[args.length - 1]) === 'function') {
+ this._find.apply(this, args);
+ }else {
+ return new SkinCursor(null, this, args);
+ }
+};
+
67 lib/mongoskin/cursor.js
@@ -0,0 +1,67 @@
+var __slice = Array.prototype.slice,
+ events = require('events'),
+ Cursor = require('mongodb').Cursor,
+ STATE_CLOSE = 0,
+ STATE_OPENNING = 1,
+ STATE_OPEN = 2;
+
+var SkinCursor = exports.SkinCursor = function(cursor, skinCollection, args ) {
+ this.cursor = cursor;
+ this.skinCollection = skinCollection;
+ this.args = args;
+ this.emitter = new events.EventEmitter();
+ if (!cursor) {
+ this.state = STATE_CLOSE;
+ }else {
+ this.state = STATE_OPEN;
+ }
+}
+
+SkinCursor.prototype.open = function(fn) {
+ switch (this.state) {
+ case STATE_OPEN:
+ return fn(null, this.cursor);
+ case STATE_OPENNING:
+ return this.emitter.addListener('open', fn);
+ case STATE_CLOSE:
+ default:
+ var that = this;
+ this.emitter.addListener('open', fn);
+ this.state = STATE_OPENNING;
+ this.skinCollection.open(function(err, collection) {
+ if (err) {
+ thas.state = STATE_CLOSE;
+ that.emitter.emit('open', err);
+ }
+ // copy args
+ var args = that.args.slice();
+ args.push(function(err, cursor) {
+ if (cursor) {
+ that.state = STATE_OPEN;
+ that.cursor = cursor;
+ }
+ that.emitter.emit('open', err, cursor);
+ });
+
+ collection.find.apply(collection, args);
+ });
+ }
+};
+
+var bindSkin = function(name, method) {
+ SkinCursor.prototype[name] = function() {
+ var args = arguments.length > 0 ? __slice.call(arguments, 0) : [];
+ return this.open(function(err, cursor) {
+ if (err) {
+ args[args.length - 1](err);
+ } else {
+ method.apply(cursor, args);
+ }
+ });
+ };
+};
+
+for (var name in Cursor.prototype) {
+ var method = Cursor.prototype[name];
+ bindSkin(name, method);
+}
166 lib/mongoskin/db.js
@@ -0,0 +1,166 @@
+var __slice = Array.prototype.slice,
+ mongo = require('mongodb'),
+ events = require('events'),
+ SkinAdmin = require('./admin').SkinAdmin,
+ SkinCollection = require('./collection').SkinCollection,
+ Db = mongo.Db,
+ Server = mongo.Server,
+
+ STATE_CLOSE = 0,
+ STATE_OPENNING = 1,
+ STATE_OPEN = 2;
+
+var _extend = function(destination, source) {
+ var property, value;
+ for (property in source) {
+ value = source[property];
+ destination[property] = value;
+ }
+ return destination;
+};
+
+var SkinServer = exports.SkinServer = function(server) {
+ this.server = server;
+ this._cache_ = [];
+};
+
+SkinServer.prototype.db = function(name, username, password) {
+ var key = (username || '') + '@' + name;
+ var skinDb = this._cache_[key];
+ if (!skinDb || skinDb.fail) {
+ var db = new Db(name, this.server, {native_parser: BSONNative !== undefined});
+ skinDb = new SkinDb(db, username, password);
+ this._cache_[key] = skinDb;
+ }
+ return skinDb;
+};
+
+var SkinDb = exports.SkinDb = function(db, username, password) {
+ this.db = db;
+ this.username = username;
+ this.password = password;
+ this.state = STATE_CLOSE;
+ this.emitter = new events.EventEmitter();
+ this.admin = new SkinAdmin(this);
+ this._collections = {};
+};
+
+SkinDb.prototype.open = function(fn) {
+ switch (this.state) {
+
+ case STATE_OPEN:
+ return fn(null, this.db);
+
+ case STATE_OPENNING:
+ return this.emitter.addListener('open', fn);
+
+ case STATE_CLOSE:
+ default:
+ var that = this;
+ var onDbOpen = function(err, db) {
+ if (!err && db) {
+ that.state = STATE_OPEN;
+ that.db = db;
+ }else {
+ that.state = STATE_CLOSE;
+ }
+ return that.emitter.emit('open', err, db);
+ };
+
+ this.emitter.addListener('open', fn);
+ this.state = STATE_OPENNING;
+
+ return this.db.open(function(err, db) {
+ if (db && that.username) {
+ //do authenticate
+ return db.authenticate(that.username, that.password, function(err) {
+ return onDbOpen(err, db);
+ });
+ } else {
+ return onDbOpen(err, db);
+ }
+ });
+ }
+};
+
+SkinDb.prototype.close = function() {
+ if (this.state === STATE_CLOSE) {
+ return;
+ }else if (this.state === STATE_OPEN) {
+ this.state = STATE_CLOSE;
+ this.db.close();
+ }else if (this.state === STATE_OPENNING) {
+ var that = this;
+ this.emitter.once('open', function(err, db) {
+ that.state = STATE_CLOSE;
+ db.close();
+ });
+ }
+};
+
+SkinDb.prototype.collection = function(name) {
+ var collection = this._collections[name];
+ if (!collection) {
+ this._collections[name] = collection = new SkinCollection(this, name);
+ }
+ return collection;
+};
+
+/*
+ 1. collection
+ 2. collection, extends1, extends2,... extendsn
+ 3. collection, SkinCollection
+*/
+SkinDb.prototype.bind = function() {
+ var args = Array.prototype.slice.call(arguments),
+ name = args[0];
+
+ if (typeof name !== 'string' || name.length === 0) {
+ throw new Error('Must provide name parameter for bind.');
+ }
+ if (args.length === 1) {
+ return this.bind(name, this.collection(name));
+ }
+ if (args.length === 2 && args[1].constructor === SkinCollection) {
+ this._collections[name] = args[1];
+ Object.defineProperty(this, name, {
+ value: args[1],
+ writable: false,
+ enumerable: true
+ });
+ return args[1];
+ }
+
+ for (var i = 1, len = args.length; i < len; i++) {
+ if (typeof args[i] != 'object')
+ throw new Error('the arg' + i + ' should be object, but is ' + args[i]);
+ }
+
+ var coll = this.collection(name);
+ for (var i = 0, len = args.length; i < len; i++) {
+ _extend(coll, args[i]);
+ }
+ return this.bind(name, coll);
+};
+
+var bindSkin = function(name, method) {
+ return SkinDb.prototype[name] = function() {
+ var args = arguments.length > 0 ? __slice.call(arguments, 0) : [];
+ return this.open(function(err, db) {
+ if (err) {
+ return args[args.length - 1](err);
+ } else {
+ return method.apply(db, args);
+ }
+ });
+ };
+};
+
+//bind method of mongo.Db to SkinDb
+for (var name in Db.prototype) {
+ var method = Db.prototype[name];
+ if (name !== 'bind' && name !== 'open' && name !== 'collection' && name !== 'admin') {
+ bindSkin(name, method);
+ }
+}
+
88 lib/mongoskin/index.js
@@ -0,0 +1,88 @@
+var url = require('url'),
+ db = require('./db'),
+ mongo = require('mongodb'),
+ SkinServer = db.SkinServer,
+ SkinDb = db.SkinDb,
+ Db = mongo.Db,
+ Server = mongo.Server,
+ ServerCluster = mongo.ServerCluster,
+ ServerPair = mongo.ServerPair,
+ BSONNative = mongo.BSONNative,
+ DEFAULT_PORT = 27017;
+
+var parseUrl = function(serverUrl) {
+ var serverUrl = /\w+:\/\//.test(serverUrl) ? serverUrl : 'db://' + serverUrl,
+ uri = url.parse(serverUrl, true),
+ config = {},
+ serverOptions = uri.query,
+ reconnect = serverOptions['auto_reconnect'];
+
+ config.host = uri.hostname;
+ config.port = uri.port || DEFAULT_PORT;
+ config.database = uri.pathname.replace(/\//g, '');
+ config.options = {};
+ config.options['auto_reconnect'] = reconnect !== undefined &&
+ reconnect != 'false' && reconnect != 'no' && reconnect != 'off';
+ if (uri && uri.auth) {
+ var auth = uri.auth.split(':');
+ config.username = auth[0];
+ config.password = auth[1];
+ }
+ return config;
+};
+
+var parseServer = function(serverUrl) {
+ var config = parseUrl(serverUrl);
+ return new Server(config.host, config.port, config.options);
+};
+
+function MongoSkin() {
+ this._preBindArgs = [];
+}
+
+MongoSkin.prototype.bind = function() {
+ var args = 1 <= arguments.length ? Array.prototype.slice.call(arguments, 0) : [];
+ return this._preBindArgs.push(args);
+};
+
+MongoSkin.prototype.pair = function(left, right) {
+ return new SkinServer(new ServerPair(parseServer(left), parseServer(right)));
+};
+
+MongoSkin.prototype.cluster = function() {
+ var servers = [];
+ for (i = 0, len = arguments.length; i < len; i++) {
+ servers.push(parseServer(arguments[i]));
+ }
+ return new SkinServer(new ServerCluster(servers));
+};
+
+MongoSkin.prototype.db = function(serverUrl) {
+ var config = parseUrl(serverUrl);
+ if (!config.database) {
+ throw new Error('Please provide a database to connect to.');
+ }
+ var server = new Server(config.host, config.port, config.options);
+ var db = new Db(config.database, server, {
+ native_parser: BSONNative !== undefined
+ });
+ skindb = new SkinDb(db, config.username, config.password);
+ for (i = 0, len = this._preBindArgs.length; i < len; i++) {
+ skindb.bind.apply(skindb, this._preBindArgs[i]);
+ }
+ return skindb;
+};
+
+mongoskin = new MongoSkin();
+
+['db', 'collection', 'cursor', 'admin'].forEach(function(path) {
+ var foo, module, name;
+ module = require('./' + path);
+ for (name in module) {
+ foo = module[name];
+ mongoskin[name] = foo;
+ }
+});
+
+module.exports = mongoskin;
+
14 package.json
@@ -0,0 +1,14 @@
+{ "name" : "mongoskin"
+, "description" : "The future layer above node-mongodb-native"
+, "version" : "0.1.0"
+, "author" : "Jason Green<guileen@gmail.com>"
+, "repository" : { "type" : "git"
+ , "url" : "git://github.com/guileen/node-mongoskin.git"}
+, "bugs" : { "mail" : "guileen@gmail.com"
+ , "web" : "http://github.com/guileen/node-mongoskin/issues"}
+, "main" : "index"
+, "keywords": [ "mongodb" ]
+, "engines" : { "node" : "*" }
+, "dependencies" : { "mongodb" : "*" }
+}
+
Please sign in to comment.
Something went wrong with that request. Please try again.