UNPKG

mongoscope-client

Version:
1,823 lines (1,555 loc) 360 kB
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ window.mongoscope = require('../'); },{"../":2}],2:[function(require,module,exports){ var Client = require('./lib/client'), pkg = require('./package.json'); module.exports = function(config){ config = config || {}; if(typeof config !== 'object'){ config = {scope: config}; } config.scope = config.scope || 'http://localhost:29017'; config.seed = config.seed || 'mongodb://localhost:27017'; return new Client(config); }; module.exports.version = pkg.version; },{"./lib/client":4,"./package.json":84}],3:[function(require,module,exports){ var Client = require('../client'), assert = require('assert'), types = { ns: require('mongodb-ns'), uri: require('mongodb-uri') }; var clients = {}, defaultClient = null; var _mongodb = function(resource){ var uri = _result(resource, 'mongodb'); if(!uri) return null; if(uri.indexOf('mongodb://') !== 0){ uri = 'mongodb://' + uri; } var info = types.uri.parse(uri), ns = null; resource.mongodb = info.hosts[0].host + ':' + info.hosts[0].port; if(info.database && !resource.url){ ns = types.ns(info.database); if(ns.collection){ resource.url = '/collections/' + ns.toString() + '/find'; } else { resource.url = '/databases/' + ns.database; } } return resource; }; var _result = function(resource, key){ return (typeof resource[key] === 'function') ? resource[key]() : resource[key]; }; function use(resource){ var endpoint, config, seed, clientId; assert(resource, 'Missing backbone resource'); if(!resource.mongoscope && !resource.mongodb){ assert(defaultClient, 'No default mongoscope client set'); return defaultClient; } if(resource.mongodb && !resource.url){ _mongodb(resource); } endpoint = _result(resource, 'mongoscope') || defaultClient.config.scope; assert(endpoint, 'Where is mongoscope running?'); config = {seed: _result(resource, 'mongodb'), scope: endpoint}; clientId = endpoint + '/' + seed; if(!clients[clientId]) clients[clientId] = new Client(config); return clients[clientId]; } function sync(method, model, options){ var url = options.url, // Backbone resources will set the current closures context to // the resource instance. resource = this, client = options.client || use(resource), ender = function(err, res){ if(err){ return options.error(err); } options.success(res); }; if(method !== 'read'){ throw new Error('mongoscope is readonly, so create, update, ' + 'patch, and delete sync methods are not available.'); } if(!options.url){ url = _result(resource, 'url'); } if(!url){ throw new Error('A "url" property or function must be specified'); } var params = {}, docs = []; Object.keys(options).map(function(k){ if(['error', 'success', 'client', 'parse'].indexOf(k) === -1){ params[k] = options[k]; } }); if(method === 'read'){ if(!options.all) return client.get(url, params, ender); return client.get(url, params) .on('error', ender) .on('data', function(doc){docs.push(doc);}) .on('end', function(){ ender(null, docs); }); } if(method === 'destroy'){ var info = client.resolve(url); if(!info) throw new Error('Could not resolve ' + url); var handler = info[0], args = info[1]; if(!handler.destroy) throw new Error('No destory hnadler found for' + handler); args.push(ender); return handler.destroy.apply(client, args); } } module.exports = function(client){ defaultClient = client; return module.exports; }; module.exports.Model = { sync: sync }; module.exports.Collection = { sync: sync }; module.exports.ReadableStream = { subscription: null, subscribe: function(){ var resource = this, client = use(resource), url = (typeof resource.url === 'function') ? resource.url() : resource.url; this.subscription = client.get(url) .on('error', function(err){ resource.trigger('error', err, resource, {client: client}); }) .on('data', function(data){ if (!resource.set(data)) return false; resource.trigger('sync', resource, data, {client: client}); }); // If the client context changes, move our subscription. this.subscription.client.on('change', function(){ this.unsubscribe(); this.subscribe(); }.bind(this)); return resource; }, unsubscribe: function(){ if(!this.subscription) return this; this.subscription.close(); this.subscription = null; return this; } }; module.exports.clients = clients; },{"../client":4,"assert":11,"mongodb-ns":37,"mongodb-uri":38}],4:[function(require,module,exports){ (function (process){ var request = require('superagent'), util = require('util'), EventEmitter = require('events').EventEmitter, Token = require('./token'), Context = require('./context'), createRouter = require('./router'), clientCursor = require('./cursor'), Subscription = require('./subscription'), assert = require('assert'), socketio = require('socket.io-client'), pkg = require('../package.json'), types = {ns: require('mongodb-ns')}, valid = require('./valid'), debug = require('debug')('mongoscope:client'); module.exports = Client; function prepare(opts, fn){ opts = opts || {}; if(typeof opts === 'function'){ fn = opts; opts = {}; } } function Client(opts){ if(!(this instanceof Client)) return new Client(opts); assert(opts.seed, 'Missing `seed` config value'); assert(opts.scope, 'Missing `scope` config value'); this.config = { seed: opts.seed, scope: opts.scope, driver: { name: pkg.name, version: pkg.version, lang: 'javascript' } }; debug('new connection', this.config); this.context = new Context(); this.readable = false; this.original = true; this.dead = false; this.closed = false; this.token = new Token(this.config) .on('readable', this.onTokenReadable.bind(this)) .on('error', this.onTokenError.bind(this)); debug('waiting for token to become readable'); } util.inherits(Client, EventEmitter); Client.prototype.close = function(fn){ if(this.io) this.io.close(); this.emit('close'); this.closed = true; this.token.close(fn); }; /** * Get details of the instance you're currently connected to * like database_names, results of the hostInfo and buildInfo mongo commands. * * @stability production */ Client.prototype.instance = function(opts, fn){ opts = opts || {}; if(typeof opts === 'function'){ fn = opts; opts = {}; } return this.read('/', opts, fn); }; /** * List all deployments this mongoscope instance has connected to. * * @stability production */ Client.prototype.deployments = function(opts, fn){ opts = opts || {}; if(typeof opts === 'function'){ fn = opts; opts = {}; } return this.read('/deployments', opts, fn); }; /** * Get the sharding info for the cluster the instance you're connected * to is a member of, similar to the `printShardingStatus()` helper function * in the mongo shell. * * @stability development */ Client.prototype.sharding = function(opts, fn){ opts = opts || {}; if(typeof opts === 'function'){ fn = opts; opts = {}; } return this.read('/sharding', opts, fn); }; /** * View current state of all members and oplog details. * * If called as a stream, events will be emitted for membership events. * * @todo: make examples * * @stability development * @streamable */ Client.prototype.replication = function(opts, fn){ opts = opts || {}; if(typeof opts === 'function'){ fn = opts; opts = {}; } opts._streamable = true; return this.read('/replication', opts, fn); }; /** * Get oplog entries. * * @todo: make examples * * @option {Number} since Epoch time lower bounds default `Date.now() - 1000 * 60` * @option {Array} filters List of tuples `({key}, {regex})` default `[]` * * @stability prototype * @streamable */ Client.prototype.oplog = function(opts, fn){ opts = opts || {}; if(typeof opts === 'function'){ fn = opts; opts = {}; } var params = { _streamable: true, since: opts.since !== undefined ? JSON.stringify(opts.since) : opts.since, filters: opts.filters !== undefined ? JSON.stringify(opts.filters) : opts.filters }; return this.read('/replication/oplog', params, fn); }; /** * Capture the deltas of top over `opts.interval` ms. * * @option {Number} interval Duration of sample in ms default `1000` * * @stability development * @streamable */ Client.prototype.top = function(opts, fn){ opts = opts || {}; if(typeof opts === 'function'){ fn = opts; opts = {}; } opts._streamable = true; return this.read('/top', opts, fn); }; /** * A structured view of the ramlog. * * @stability development * @streamable */ Client.prototype.log = function(opts, fn){ opts = opts || {}; if(typeof opts === 'function'){ fn = opts; opts = {}; } opts._streamable = true; return this.read('/log', opts, fn); }; /** * List collection names and stats. * * @param {String} name * * @stability production */ Client.prototype.database = function database(name, opts, fn){ opts = opts || {}; if(typeof opts === 'function'){ fn = opts; opts = {}; } return this.read('/databases/' + name, opts, fn); }; Client.prototype.destroyDatabase = function(name, fn){ return this.crud('del', '/databases/' + name, {}, fn); }; /** * Collection stats * * @param {String} ns * * @stability production */ Client.prototype.collection = function collection(ns, opts, fn){ opts = opts || {}; if(typeof opts === 'function'){ fn = opts; opts = {}; } if(!valid.ns(ns)){ return fn(new TypeError('Invalid namespace string `' + ns + '`')); } return this.read('/collections/' + ns, opts, fn); }; Client.prototype.createCollection = function(ns, opts, fn){ opts = opts || {}; if(typeof opts === 'function'){ fn = opts; opts = {}; } if(!valid.ns(ns)){ return fn(new TypeError('Invalid namespace string `' + ns + '`')); } var _ns = types.ns(ns); return this.crud('post', '/collections/' + _ns.database, { name: _ns.collection, capped: opts.capped || false, max: opts.max || null, size: opts.size || null }, fn); }; Client.prototype.destroyCollection = function(ns, fn){ if(!valid.ns(ns)){ return fn(new TypeError('Invalid namespace string `' + ns + '`')); } return this.crud('del', '/collections/' + ns, {}, fn); }; Client.prototype.updateCollection = function(ns, data, fn){ if(!valid.ns(ns)){ return fn(new TypeError('Invalid namespace string `' + ns + '`')); } return this.crud('put', '/collections/' + ns, data, fn); }; // Currently running operations. // // @stability development // @streamable Client.prototype.ops = function(opts, fn){ opts = opts || {}; if(typeof opts === 'function'){ fn = opts; opts = {}; } opts._streamable = true; return this.read('/ops', opts, fn); }; // Kill a currently running operation. // // @stability development Client.prototype.destroyOp = function(opId, opts, fn){ opts = opts || {}; if(typeof opts === 'function'){ fn = opts; opts = {}; } return this.crud('del', '/ops/' + opId, {}, fn); }; /** * Index details * * @param {String} ns * @param {String} name The index name * * @stability prototype */ Client.prototype.index = function(ns, name, fn){ if(!valid.ns(ns)){ return fn(new TypeError('Invalid namespace string `' + ns + '`')); } return this.read('/indexes/' + ns + '/' + name, {}, fn); }; Client.prototype.createIndex = function(ns, name, opts, fn){ opts = opts || {}; if(typeof opts === 'function'){ fn = opts; opts = {}; } if(!valid.ns(ns)){ return fn(new TypeError('Invalid namespace string `' + ns + '`')); } var params = {field: name, options: opts}; return this.crud('post', '/indexes/' + ns, params, fn); }; Client.prototype.updateIndex = function(ns, name, opts, fn){ opts = opts || {}; if(typeof opts === 'function'){ fn = opts; opts = {}; } if(!valid.ns(ns)){ return fn(new TypeError('Invalid namespace string `' + ns + '`')); } var params = {field: name, options: opts}; return this.crud('put', '/indexes/' + ns, params, fn); }; Client.prototype.destroyIndex = function(ns, name, fn){ if(!valid.ns(ns)){ return fn(new TypeError('Invalid namespace string `' + ns + '`')); } return this.crud('del','/indexes/' + ns + '/' + name, {}, fn); }; Client.prototype.getDocument = function(ns, _id, fn){ if(!valid.ns(ns)){ return fn(new TypeError('Invalid namespace string `' + ns + '`')); } return this.read('/documents/' + ns + '/' + _id, {}, fn); }; Client.prototype.updateDocument = function(ns, _id, data, fn){ if(!valid.ns(ns)){ return fn(new TypeError('Invalid namespace string `' + ns + '`')); } return this.crud('put', '/documents/' + ns + '/' + _id, data, fn); }; Client.prototype.createDocument = function(ns, data, fn){ if(!valid.ns(ns)){ return fn(new TypeError('Invalid namespace string `' + ns + '`')); } return this.crud('post', '/documents/' + ns, data, fn); }; Client.prototype.destroyDocument = function(ns, _id, fn){ if(!valid.ns(ns)){ return fn(new TypeError('Invalid namespace string `' + ns + '`')); } return this.crud('del', '/documents/' + ns + '/' + _id, {}, fn); }; /** * Run a query on `db.collection`. * * @param {String} ns * * @option {Object} query default `{}` * @option {Number} limit default `10`, max 200 * @option {Number} skip default 0 * @option {Boolean} explain Return explain instead of documents default `false` * @option {Object} sort `{key: (1|-1)}` spec default `null` * @option {Object} fields * @option {Object} options * @option {Number} batchSize * * @stability production * @streamable */ Client.prototype.find = function(ns, opts, fn){ opts = opts || {}; if(typeof opts === 'function'){ fn = opts; opts = {}; } if(!valid.ns(ns)){ return fn(new TypeError('Invalid namespace string `' + ns + '`')); } if(fn){ return this.read('/collections/' + ns + '/find', { query: JSON.stringify((opts.query || {})), limit: (opts.limit || 10), skip: (opts.skip || 0), explain: (opts.explain || false), sort: JSON.stringify((opts.sort || undefined)), fields: JSON.stringify((opts.fields || undefined)), options: JSON.stringify((opts.options || undefined)), batchSize: JSON.stringify((opts.batchSize || undefined)) }, fn); } var client = this, cursor = clientCursor(ns, opts.query || {}, opts.limit || 100, opts.skip || 0, opts.fields || null, opts.options || null); cursor.requestMore = function(cb){ var p = { query: cursor.query, skip: cursor.nToSkip, limit: cursor.nToReturn, fields: cursor.fieldsToReturn, options: cursor.queryOptions }; client.find(ns, p, function(err, res){ if(err) return cb(err); cursor.batch = cursor.createBatch(); cursor.batch.nReturned = res.length; cursor.batch.data = res; cursor.nToSkip += cursor.nToReturn; cb(); }); }; return cursor; }; /** * Run a count on `db.collection`. * * @param {String} ns * @param {Object} opts * @param {Function} fn * @option {Object} query default `{}` * @option {Number} limit default `10`, max 200 * @option {Number} skip default 0 * @option {Boolean} explain Return explain instead of documents default `false` * @option {Object} sort `{key: (1|-1)}` spec default `null` * @option {Object} fields * @option {Object} options * @option {Number} batchSize * @stability production */ Client.prototype.count = function(ns, opts, fn){ opts = opts || {}; if(typeof opts === 'function'){ fn = opts; opts = {}; } if(!valid.ns(ns)){ return fn(new TypeError('Invalid namespace string `' + ns + '`')); } var params = { query: JSON.stringify((opts.query || {})), limit: (opts.limit || 10), skip: (opts.skip || 0), explain: (opts.explain || false), sort: JSON.stringify((opts.sort || null)), fields: JSON.stringify((opts.fields || null)), options: JSON.stringify((opts.options || null)), batchSize: JSON.stringify((opts.batchSize || null)) }; return this.read('/collections/' + ns+ '/count', params, fn); }; /** * Run an aggregation pipeline on `db.collection`. * * @param {String} ns * @param {Array} pipeline * @param {Object} opts * @option {Boolean} explain * @option {Boolean} allowDiskUse * @option {Object} cursor * @stability development */ Client.prototype.aggregate = function(ns, pipeline, opts, fn){ if(!Array.isArray(pipeline)){ return fn(new TypeError('pipeline must be an array')); } opts = opts || {}; if(typeof opts === 'function'){ fn = opts; opts = {}; } if(!valid.ns(ns)){ return fn(new TypeError('Invalid namespace string `' + ns + '`')); } return this.read('/collections/' + ns + '/aggregate', { pipeline: JSON.stringify(pipeline), explain: (opts.explain || false), allowDiskUse: JSON.stringify((opts.allowDiskUse || null)), cursor: JSON.stringify((opts.cursor || null)), _streamable: true }, fn); }; /** * Use [resevoir sampling](http://en.wikipedia.org/wiki/Reservoir_sampling) to * get a slice of documents from a collection efficiently. * * @param {String} ns * @param {Object} opts * @option {Number} size default: 5 * @stability prototype */ Client.prototype.sample = function(ns, opts, fn){ opts = opts || {}; if(typeof opts === 'function'){ fn = opts; opts = {}; } if(!valid.ns(ns)){ return fn(new TypeError('Invalid namespace string `' + ns + '`')); } return this.read('/collections/' + ns + '/sample', { size: opts.size || 5 }, fn); }; /** * Convenience to get 1 document via `Client.prototype.sample`. * * @param {String} ns * @param {Object} opts * @stability prototype */ Client.prototype.random = function(ns, opts, fn){ opts = opts || {}; if(typeof opts === 'function'){ fn = opts; opts = {}; } if(!valid.ns(ns)){ return fn(new TypeError('Invalid namespace string `' + ns + '`')); } return this.sample(ns, {size: 1}, function(err, docs){ if(err) return fn(err); fn(null, docs[0]); }); }; /** * Get or stream a group of analytics. * * @param {String} group One of `Client.prototype.analytics.groups` * @stability prototype * @streamable */ Client.prototype.analytics = function(group, opts, fn){ opts = opts || {}; if(typeof opts === 'function'){ fn = opts; opts = {}; } if(this.analytics.groups.indexOf(group) === -1){ var msg = 'Unknown analytics group `'+group+'`'; if(fn) return fn(new Error(msg)); throw new Error(msg); } return this.read('/analytics/' + group, { interval: opts.interval || 1000 }, fn); }; Client.prototype.analytics.groups = [ 'durability', 'operations', 'memory', 'replication', 'network', 'indexes' ]; /** * Working set size estimator. * * @stability prototype */ Client.prototype.workingSet = function(opts, fn){ opts = opts || {}; if(typeof opts === 'function'){ fn = opts; opts = {}; } return this.read('/working-set', {}, fn); }; /** * Maps backbone.js/express.js style routes to `Client.prototype` methods. * * @api private */ Client.prototype.routes = { '/instance': 'instance', '/deployments': 'deployments', '/deployments/:deployment_id': 'deployment', '/databases/:database': 'database', '/collections/:ns': 'collection', '/collections/:ns/count': 'count', '/collections/:ns/find': 'find', '/collections/:ns/aggregate': 'aggregate', '/log': 'log', '/top': 'top', '/ops': 'ops', '/replication': 'replication', '/sharding': 'sharding' }; /** * Route `fragment` to a call on `Client.prototype`, which is substantially * easier for users on the client-side. More detailled usage is available * in the [backbone.js adapter](/lib/backbone.js). * * @param {String} fragment One of `Client.prototype.routes` * @param {Object} [params] * @param {Function} [fn] * * @stability development */ Client.prototype.get = function(fragment, opts, fn){ opts = opts || {}; if(typeof opts === 'function'){ fn = opts; opts = {}; } var resolved = this.resolve(fragment), handler = resolved[0], args = resolved[1]; args.push.apply(args, [opts, fn]); return handler.apply(this, args); }; Client.prototype.resolve = function(fragment){ if(!this.router) this.router = createRouter(this.routes); var route = this.router.resolve(fragment); return [this[route.method], route.args]; }; Object.defineProperty(Client.prototype, 'backbone', {enumerable: true, writeable: false, configurable: false, get: function(){ if(!this.adapters) this.adapters = {}; if(!this.adapters.backbone){ this.adapters.backbone = require('./adapters/backbone.js')(this); } return this.adapters.backbone; }}); /** * Point at a difference instance. * * @param {String} seed */ Client.prototype.connect = function(seed){ if(seed === this.config.seed){ debug('already connected to ' + seed); return this; } this.readable = false; this.token.close(); this.original = false; this.config.seed = seed; this.token = new Token(this.config) .on('readable', this.onTokenReadable.bind(this)) .on('error', this.emit.bind(this, 'error')); return this; }; /** * All read requests come through here. * Handles queuing if still connecting and promoting streamables. * * @param {String} path Everything under `/api/v1` automatically prefixing instance. * @param {Object} [params] * @param {Function} [fn] * * @api private */ Client.prototype.read = function(path, params, fn){ debug('READ', path, params); if(this.dead) return fn(this.dead); if(this.closed) return fn(new Error('Client already closed')); if(!this.readable) return this.on('readable', this.read.bind(this, path, params, fn)); if(typeof params === 'function'){ fn = params; params = {}; } var instance_id = this.context.get('instance_id'), streamable = params._streamable; delete params._streamable; if(!fn && !streamable){ var msg = 'not streamable and missing callback'; if(fn) return fn(new Error(msg)); throw new Error(msg); } if(streamable && !fn) return new Subscription(this, path, params); path = (path === '/') ? '/' + instance_id : (path !== '/deployments') ? '/' + instance_id + path : path; return request.get(this.config.scope + '/api/v1' + path) .set('Accept', 'application/json') .set('Authorization', 'Bearer ' + this.token.toString()) .query(params) .end(this.ender(fn)); }; Client.prototype.ender = function(fn){ return function(err, res){ if(!err && res.status >= 400){ err = new Error(res.body ? res.body.message : res.text); Error.captureStackTrace(err, Client.prototype.ender); err.status = res.status; } fn.apply(null, [err, (res && res.body), res]); }; }; Client.prototype.crud = function(method, path, data, fn){ prepare(data, fn); if(this.dead) return fn(this.dead); if(!this.readable) return this.on('readable', this.crud.bind(this, method, path, data, fn)); var instance_id = this.context.get('instance_id'), req; req = request[method](this.config.scope + '/api/v1/' + instance_id + path) .set('Accept', 'application/json') .set('Authorization', 'Bearer ' + this.token.toString()); if(method !== 'del') req.type('json').send(data); return req.end(this.ender(fn)); }; /** * When we've acquired a security token, do child connections (eg socketio) * and unload handlers. * * If we're reusing an instance, but the user has changed context, * emit `change` so any open streams can easily end the old one * and open a new stream on the current one. * * @api private */ Client.prototype.onTokenReadable = function(){ debug('token now readable', this.token.session); this.context.set(this.token.session); this.io = socketio(this.config.scope); this.readable = true; this.emit('readable', this.token.session); if(!this.original){ this.emit('change'); } else { if(window && window.document){ if(window.attachEvent){ window.attachEvent('onunload', this.onUnload.bind(this)); } else if(window.addEventListener){ window.addEventListener('beforeunload', this.onUnload.bind(this)); } } else if(process && process.on){ process.on('exit', this.onUnload.bind(this)); } } }; /** * On browser window unload or process exit if running in nodejs, * make sure we clean up after ourselves. * * @api private */ Client.prototype.onUnload = function(){ if(!this.closed) this.close(); }; /** * When a token error occurs, the browser can't provide us with good * context in the error, so for now assuming all token errors * mean mongoscope is not running. * * @param {Error} err * @api private */ Client.prototype.onTokenError = function(err){ this.dead = err; this.dead.message += ' (mongoscope server dead at '+this.config.scope+'?)'; this.emit('error', err); }; }).call(this,require("FWaASH")) },{"../package.json":84,"./adapters/backbone.js":3,"./context":5,"./cursor":6,"./router":7,"./subscription":8,"./token":9,"./valid":10,"FWaASH":19,"assert":11,"debug":36,"events":17,"mongodb-ns":37,"socket.io-client":39,"superagent":81,"util":35}],5:[function(require,module,exports){ (function (process){ var util = require('util'), EventEmitter = require('events').EventEmitter, debug = require('debug')('mongoscope:client:context'); module.exports = Context; function Context(){ this.data = { deployment_id: null, instance_id: null }; } util.inherits(Context, EventEmitter); Context.prototype.get = function(){ var args = Array.prototype.slice.call(arguments, 0); if(args.length === 1) return this.data[args[0]]; var res = {}; args.map(function(key){ res[key] = this.data[key]; }.bind(this)); return res; }; Context.prototype.set = function(obj){ var changed = false, self = this, prev = {}; for(var k in obj){ if(obj[k] !== this.data[k]){ prev[k] = this.data[k]; this.data[k] = obj[k]; changed = true; } } if(changed){ process.nextTick(function(){ self.emit('change', {incoming: obj, previous: prev}); }); } return this; }; }).call(this,require("FWaASH")) },{"FWaASH":19,"debug":36,"events":17,"util":35}],6:[function(require,module,exports){ var util = require('util'), stream = require('stream'), debug = require('debug')('mongoscope:client:cursor'); function Batch() { if(!(this instanceof Batch)) return new Batch(); this.message = {}; this.nReturned = 0; this.pos = -1; this.data = []; } function ClientCursor(ns, query, nToReturn, nToSkip, fieldsToReturn, queryOptions) { if(!(this instanceof ClientCursor)){ return new ClientCursor(ns, query, nToReturn, nToSkip, fieldsToReturn, queryOptions); } ClientCursor.super_.call(this, {objectMode: true}); this.ns = ns; this.query = query || {}; this.nToReturn = nToReturn || 10; this.nToSkip = nToSkip || 0; this.fieldsToReturn = fieldsToReturn || null; this.queryOptions = queryOptions || {}; this.cursorId = 0; this.batch = new Batch(); } util.inherits(ClientCursor, stream.Readable); ClientCursor.prototype.createBatch = Batch; ClientCursor.prototype._read = function(){ this.next(function(err, doc){ if(err) return this.emit('error'); this.push(doc); }.bind(this)); }; ClientCursor.prototype.next = function (fn) { this.more(function(err, has){ if(err) return fn(err); if(!has) return fn(null, null); this.batch.pos++; var o = this.batch.data[this.batch.pos]; fn(null, o); }.bind(this)); }; ClientCursor.prototype.more = function (fn) { if(this.batch.pos >= this.nToReturn) { return fn(null, false); } if(this.batch.pos >= 0 && this.batch.pos < (this.batch.nReturned-1)){ return fn(null, true); } this.requestMore(function(err){ if(err) return fn(err); fn(null, this.batch.pos < this.batch.nReturned); }.bind(this)); }; // Oh hey! we found some networking! ClientCursor.prototype.requestMore = function (fn){ return fn(new Error('Client should override this')); }; ClientCursor.prototype.hasNext = function (fn) { this.more(fn); }; ClientCursor.prototype.objsLeftInBatch = function () { return this.batch.nReturned - this.batch.pos; }; ClientCursor.prototype.moreInCurrentBatch = function () { return this.objsLeftInBatch() > 0; }; module.exports = ClientCursor; },{"debug":36,"stream":33,"util":35}],7:[function(require,module,exports){ // ## Stateful API // // Makes client apps so much simpler because they need extremely little domain // logic to use the hell out of an api. For usage examples, see the // [backbone.js adapter](/lib/backbone.js). module.exports = function(def){ var _routes = [], optionalParam = /\((.*?)\)/g, namedParam = /(\(\?)?:\w+/g, splatParam = /\*\w+/g, escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; Object.keys(def).map(function(spec){ var regex = spec.replace(escapeRegExp, '\\$&') .replace(optionalParam, '(?:$1)?') .replace(namedParam, function(match, optional){ return optional ? match : '([^/?]+)'; }) .replace(splatParam, '([^?]*?)'); _routes.push({ spec: spec, method: def[spec], regex: new RegExp('^' + regex + '(?:\\?([\\s\\S]*))?$') }); }); function params(route, fragment){ var p = route.regex.exec(fragment).slice(1); if(!p[0]){ return []; } return p.map(function(param, i){ if (i === p.length - 1) return param || null; return param ? decodeURIComponent(param) : null; }).filter(function(v){ return v !== null; }); } return { resolve: function(fragment){ var route = null; _routes.every(function(rule){ if(rule.regex.test(fragment)){ route = { method: rule.method, args: params(rule, fragment) }; return false; } return true; }); if(!route){ throw new Error('No route found for: ' + fragment); } return route; } }; }; },{}],8:[function(require,module,exports){ var util = require('util'), stream = require('stream'); module.exports = Subscription; var _lastId = 0; var generateId = function(){ return _lastId++; }; function Subscription(client, url, opts){ if(!(this instanceof Subscription)) return new Subscription(client, url, opts); Subscription.super_.call(this, {objectMode: true}); opts = opts || {}; this.client = client; this.url = url; this.payload = this.client.context.get('token', 'instance_id'); Object.keys(opts).map(function(key){ this.payload[key] = opts.key; }.bind(this)); this.id = generateId(); this.listening = false; this.debug = require('debug')('mongoscope:client:subscription:' + this.id); client.on('close', this.close.bind(this)); this.debug('subscription created for ' + this.url); } util.inherits(Subscription, stream.Readable); Subscription.prototype._read = function(){ if(this.listening) return this; this.listening = true; this.debug('sending payload', this.payload); this.client.io .on(this.url, this.onData.bind(this)) .on(this.url + '/error', this.onError.bind(this)) .emit(this.url, this.payload); return this; }; Subscription.prototype.onData = function(data){ this.debug('got data', data); this.push(data); }; Subscription.prototype.onError = function(data){ var err = new Error(); err.code = data.code; err.http = data.http; err.message = data.message; Error.captureStackTrace(err, this); this.emit('error', err); }; Subscription.prototype.close = function(){ // @todo: check io.closed instead? if(!this.client.io.connected){ this.debug('client already closed'); return; } this.client.io .off(this.url) .on(this.url + '/unsubscribe_complete', function(){ this.push(null); }.bind(this)) .emit(this.url + '/unsubscribe', this.payload); }; },{"debug":36,"stream":33,"util":35}],9:[function(require,module,exports){ (function (process){ var request = require('superagent'), util = require('util'), EventEmitter = require('events').EventEmitter, debug = require('debug')('mongooscope:client:token'); module.exports = Token; function Token(config){ if(!(this instanceof Token)) return new Token(config); this.config = config; this.expirationRedLine = 15 * 1000; this.session = {}; this.readable = false; debug('creating token', this.config); process.nextTick(function(){ this.bake(function(err, res){ if(err) return this.emit('error', err); this.session = res; this.schedule(); this.readable = true; this.emit('readable'); }.bind(this)); }.bind(this)); } util.inherits(Token, EventEmitter); Token.prototype.toString = function(){ return this.session.token; }; Object.defineProperty(Token.prototype, 'token', {get: function(){ return this.session.token; }}); var defaultFn = function(err){ if(err) return console.error(err); }; Token.prototype.close = function(fn){ fn = fn || defaultFn; clearTimeout(this.refreshTimeout); request.del(this.config.scope + '/api/v1/token') .set('Accept', 'application/json') .set('Authorization', 'Bearer ' + this.session.token) .end(function(err, res){ debug('close token', err, res); fn(err, res); }); }; Token.prototype.bake = function(done){ debug('getting token for', this.config.seed); request.post(this.config.scope + '/api/v1/token') .send({seed: this.config.seed}) .set('Accept', 'application/json') .end(function(err, res){ if(err) return done(err); if(!err && res.status >= 400){ err = new Error(res.body ? res.body.message : res.text); err.code = res.status; Error.captureStackTrace(err, Token.prototype.bake); return done(err); } debug('got token response', res.body); if(!res.body.expires_at || !res.body.created_at){ return done(new Error('Malformed response. Missing expires_at or created_at')); } if(new Date(res.body.expires_at) - Date.now() < (1 * 60 * 1000)){ return done(new Error('Got an expires that is less than a minute from now.')); } done(null, res.body); }.bind(this)); }; Token.prototype.refresh = function(){ this.bake(function(err, res){ if(err) this.emit('error', err); this.session = res; debug('token refreshed successfully'); return this.schedule(); }.bind(this)); }; Token.prototype.schedule = function(){ var ms = (new Date(this.session.expires_at) - Date.now()) - this.expirationRedLine; debug('token redline in ' + ms + 'ms', (ms/1000/60) + 'minutes'); this.refreshTimeout = setTimeout(this.refresh.bind(this), ms); }; }).call(this,require("FWaASH")) },{"FWaASH":19,"debug":36,"events":17,"superagent":81,"util":35}],10:[function(require,module,exports){ var types = {ns: require('mongodb-ns')}; module.exports.ns = function(val){ var _ns = types.ns(val); return _ns.validDatabaseName && _ns.validCollectionName; }; },{"mongodb-ns":37}],11:[function(require,module,exports){ // http://wiki.commonjs.org/wiki/Unit_Testing/1.0 // // THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8! // // Originally from narwhal.js (http://narwhaljs.org) // Copyright (c) 2009 Thomas Robinson <280north.com> // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the 'Software'), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // when used in node, this will actually load the util module we depend on // versus loading the builtin util module as happens otherwise // this is a bug in node module loading as far as I am concerned var util = require('util/'); var pSlice = Array.prototype.slice; var hasOwn = Object.prototype.hasOwnProperty; // 1. The assert module provides functions that throw // AssertionError's when particular conditions are not met. The // assert module must conform to the following interface. var assert = module.exports = ok; // 2. The AssertionError is defined in assert. // new assert.AssertionError({ message: message, // actual: actual, // expected: expected }) assert.AssertionError = function AssertionError(options) { this.name = 'AssertionError'; this.actual = options.actual; this.expected = options.expected; this.operator = options.operator; if (options.message) { this.message = options.message; this.generatedMessage = false; } else { this.message = getMessage(this); this.generatedMessage = true; } var stackStartFunction = options.stackStartFunction || fail; if (Error.captureStackTrace) { Error.captureStackTrace(this, stackStartFunction); } else { // non v8 browsers so we can have a stacktrace var err = new Error(); if (err.stack) { var out = err.stack; // try to strip useless frames var fn_name = stackStartFunction.name; var idx = out.indexOf('\n' + fn_name); if (idx >= 0) { // once we have located the function frame // we need to strip out everything before it (and its line) var next_line = out.indexOf('\n', idx + 1); out = out.substring(next_line + 1); } this.stack = out; } } }; // assert.AssertionError instanceof Error util.inherits(assert.AssertionError, Error); function replacer(key, value) { if (util.isUndefined(value)) { return '' + value; } if (util.isNumber(value) && (isNaN(value) || !isFinite(value))) { return value.toString(); } if (util.isFunction(value) || util.isRegExp(value)) { return value.toString(); } return value; } function truncate(s, n) { if (util.isString(s)) { return s.length < n ? s : s.slice(0, n); } else { return s; } } function getMessage(self) { return truncate(JSON.stringify(self.actual, replacer), 128) + ' ' + self.operator + ' ' + truncate(JSON.stringify(self.expected, replacer), 128); } // At present only the three keys mentioned above are used and // understood by the spec. Implementations or sub modules can pass // other keys to the AssertionError's constructor - they will be // ignored. // 3. All of the following functions must throw an AssertionError // when a corresponding condition is not met, with a message that // may be undefined if not provided. All assertion methods provide // both the actual and expected values to the assertion error for // display purposes. function fail(actual, expected, message, operator, stackStartFunction) { throw new assert.AssertionError({ message: message, actual: actual, expected: expected, operator: operator, stackStartFunction: stackStartFunction }); } // EXTENSION! allows for well behaved errors defined elsewhere. assert.fail = fail; // 4. Pure assertion tests whether a value is truthy, as determined // by !!guard. // assert.ok(guard, message_opt); // This statement is equivalent to assert.equal(true, !!guard, // message_opt);. To test strictly for the value true, use // assert.strictEqual(true, guard, message_opt);. function ok(value, message) { if (!value) fail(value, true, message, '==', assert.ok); } assert.ok = ok; // 5. The equality assertion tests shallow, coercive equality with // ==. // assert.equal(actual, expected, message_opt); assert.equal = function equal(actual, expected, message) { if (actual != expected) fail(actual, expected, message, '==', assert.equal); }; // 6. The non-equality assertion tests for whether two objects are not equal // with != assert.notEqual(actual, expected, message_opt); assert.notEqual = function notEqual(actual, expected, message) { if (actual == expected) { fail(actual, expected, message, '!=', assert.notEqual); } }; // 7. The equivalence assertion tests a deep equality relation. // assert.deepEqual(actual, expected, message_opt); assert.deepEqual = function deepEqual(actual, expected, message) { if (!_deepEqual(actual, expected)) { fail(actual, expected, message, 'deepEqual', assert.deepEqual); } }; function _deepEqual(actual, expected) { // 7.1. All identical values are equivalent, as determined by ===. if (actual === expected) { return true; } else if (util.isBuffer(actual) && util.isBuffer(expected)) { if (actual.length != expected.length) return false; for (var i = 0; i < actual.length; i++) { if (actual[i] !== expected[i]) return false; } return true; // 7.2. If the expected value is a Date object, the actual value is // equivalent if it is also a Date object that refers to the same time. } else if (util.isDate(actual) && util.isDate(expected)) { return actual.getTime() === expected.getTime(); // 7.3 If the expected value is a RegExp object, the actual value is // equivalent if it is also a RegExp object with the same source and // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`). } else if (util.isRegExp(actual) && util.isRegExp(expected)) { return actual.source === expected.source && actual.global === expected.global && actual.multiline === expected.multiline && actual.lastIndex === expected.lastIndex && actual.ignoreCase === expected.ignoreCase; // 7.4. Other pairs that do not both pass typeof value == 'object', // equivalence is determined by ==. } else if (!util.isObject(actual) && !util.isObject(expected)) { return actual == expected; // 7.5 For all other Object pairs, including Array objects, equivalence is // determined by having the same number of owned properties (as verified // with Object.prototype.hasOwnProperty.call), the same set of keys // (although not necessarily the same order), equivalent values for every // corresponding key, and an identical 'prototype' property. Note: this // accounts for both named and indexed properties on Arrays. } else { return objEquiv(actual, expected); } } function isArguments(object) { return Object.prototype.toString.call(object) == '[object Arguments]'; } function objEquiv(a, b) { if (util.isNullOrUndefined(a) || util.isNullOrUndefined(b)) return false; // an identical 'prototype' property. if (a.prototype !== b.prototype) return false; //~~~I've managed to break Object.keys through screwy arguments passing. // Converting to array solves the problem. if (isArguments(a)) { if (!isArguments(b)) { return false; } a = pSlice.call(a); b = pSlice.call(b); return _deepEqual(a, b); } try { var ka = objectKeys(a), kb = objectKeys(b), key, i; } catch (e) {//happens when one is a string literal and the other isn't return false; } // having the same number of owned properties (keys incorporates // hasOwnProperty) if (ka.length != kb.length) return false; //the same set of keys (although not necessarily the same order), ka.sort(); kb.sort(); //~~~cheap key test for (i = ka.length - 1; i >= 0; i--) { if (ka[i] != kb[i]) return false; } //equivalent values for every corresponding key, and //~~~possibly expensive deep test for (i = ka.length - 1; i >= 0; i--) { key = ka[i]; if (!_deepEqual(a[key], b[key])) return false; } return true; } // 8. The non-equivalence assertion tests for any deep inequality. // assert.notDeepEqual(actual, expected, message_opt); assert.notDeepEqual = function notDeepEqual(actual, expected, message) { if (_deepEqual(actual, expected)) { fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual); } }; // 9. The strict equality assertion tests strict equality, as determined by ===. // assert.strictEqual(actual, expected, message_opt); assert.strictEqual = function strictEqual(actual, expected, message) { if (actual !== expected) { fail(actual, expected, message, '===', assert.strictEqual); } }; // 10. The strict non-equality assertion tests for strict inequality, as // determined by !==. assert.notStrictEqual(actual, expected, message_opt); assert.notStrictEqual = function notStrictEqual(actual, expected, message) { if (actual === expected) { fail(actual, expected, message, '!==', assert.notStrictEqual); } }; function expectedException(actual, expected) { if (!actual || !expected) { return false; } if (Object.prototype.toString.call(expected) == '[object RegExp]') { return expected.test(actual); } else if (actual instanceof expected) { return true; } else if (expected.call({}, actual) === true) { return true; } return false; } function _throws(shouldThrow, block, expected, message) { var actual; if (util.isString(expected)) { message = expected; expected = null; } try { block(); } catch (e) { actual = e; } message = (expected && expected.name ? ' (' + expected.name + ').' : '.') + (message ? ' ' + message : '.'); if (shouldThrow && !actual) { fail(actual, expected, 'Missing expected exception' + message); } if (!shouldThrow && expectedException(actual, expected)) { fail(actual, expected, 'Got unwanted exception' + message); } if ((shouldThrow && actual && expected && !expectedException(actual, expected)) || (!shouldThrow && actual)) { throw actual; } } // 11. Expected to throw an error: // assert.throws(block, Error_opt, message_opt); assert.throws = function(block, /*optional*/error, /*optional*/message) { _throws.apply(this, [true].concat(pSlice.call(arguments))); }; // EXTENSION! This is annoying to write outside this module. assert.doesNotThrow = function(block, /*optional*/message) { _throws.apply(this, [false].concat(pSlice.call(arguments))); }; assert.ifError = function(err) { if (err) {throw err;}}; var objectKeys = Object.keys || function (obj) { var keys = []; for (var key in obj) { if (hasOwn.call(obj, key)) keys.push(key); } return keys; }; },{"util/":13}],12:[function(require,module,exports){ module.exports = function isBuffer(arg) { return arg && typeof arg === 'object' && typeof arg.copy === 'function' && typeof arg.fill === 'function' && typeof arg.readUInt8 === 'function'; } },{}],13:[function(require,module,exports){ (function (process,global){ // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. var formatRegExp = /%[sdj%]/g; exports.format = function(f) { if (!isString(f)) { var objects = []; for (var i = 0; i < arguments.length; i++) { objects.push(inspect(arguments[i])); } return objects.join(' '); } var i = 1; var args = arguments; var len = args.length; var str = String(f).replace(formatRegExp, function(x) { if (x === '%%') return '%'; if (i >= len) return x; switch (x) { case '%s': return String(args[i++]); case '%d': return Number(args[i++]); case '%j': try { return JSON.stringify(args[i++]); } catch (_) { return '[Circular]'; } default: return x; } }); for (var x = args[i]; i < len; x