mongoscope-client
Version:
1,823 lines (1,555 loc) • 360 kB
JavaScript
(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