{"action":"created","comment":{"url":"https://api.github.com/repos/MRN-Code/coinstac-common/pulls/comments/55280948","id":55280948,"diff_hunk":"@@ -0,0 +1,493 @@\n+'use strict';\n+const EventEmitter = require('events').EventEmitter;\n+const Base = require('../../../base');\n+const DBRegistry = require('../../../db-registry');\n+const DBListener = require('../../../db-listener');\n+const ComputationRegistry = require('../../../../services/classes/computation-registry');\n+const Consortium = require('../../../consortium');\n+const ComputationResult = require('../../../computation/computation-result');\n+const RemoteComputationResult = require('../../../computation/remote-computation-result');\n+const LocalComputationResult = require('../../../computation/local-computation-result');\n+const Pouchy = require('pouchy');\n+const util = require('util');\n+const joi = require('joi');\n+const values = require('lodash/values');\n+const async = require('async');\n+const throttle = require('lodash/throttle');\n+\n+/**\n+ * @abstract\n+ * @class PipelineRunnerPool\n+ * @extends Base\n+ * @constructor\n+ * @description listens for changes in computation result data stores, and then\n+ * triggers PipelineRunners to resume execution using changed/new db data.\n+ * value proposition that this Pool offers to the COINSTAC ecosystem is:\n+ * - minimization of db change emitters (e.g. one emitter per member store)\n+ * - single source of instantiation and management for PipelineRunner instances\n+ * - single source of computation preparation, pre-PipelineRunner execution\n+ *\n+ * call `.init` to kickoff the Pool activity when ready\n+ *\n+ * @property {Pouchy} consortiaDB\n+ * @property {Pouchy} computationsDB\n+ * @property {object} resultsListeners DBListener instances, indexed by db name\n+ * @property {object} consortiaListener DBListener instance, used to trigger\n+ *                                      listening on a new results stores\n+ *                                      when a new consortium enters the scene\n+ * @property {object} runners PipelineRunner instances, indexed by runId\n+ * @property {object} runQueue async `queue` of jobs waiting to enter\n+ *                             PipelineRunners, indexed by runId\n+ * @property {function} on/emit/off basic EventEmitter functions\n+ */\n+function PipelineRunnerPool() {\n+    Base.apply(this, arguments);\n+    this.runQueue = {};\n+    this.runners = {};\n+    this.resultsListeners = {}; // indexed by `name` => e.g. remote-consortium-{ _.id }\n+\n+    // embed an emitter, proxy its goodies\n+    this.events = new EventEmitter();\n+    this.on = this.events.on.bind(this.events);\n+    this.off = this.events.removeListener.bind(this.events);\n+    this.emit = this.events.emit.bind(this.events);\n+\n+    this._hasAttemptedToInitialize;\n+    this._hasInitialized;\n+}\n+util.inherits(PipelineRunnerPool, Base);\n+\n+/**\n+ * PipelineRunnerPool fully initialized with sync'd critical DBs\n+ * and listeners ready-to-go!\n+ *\n+ * @event PipelineRunnerPool#ready\n+ * @type {undefined}\n+ */\n+\n+/**\n+ * PipelineRunnerPool has detected that DecentralizedComputation run is fully\n+ * complete\n+ *\n+ * @event PipelineRunnerPool#computation:complete\n+ * @type {string} runId\n+ */\n+\n+/**\n+ PipelineRunner will resume processing on its pipeline\n+\n+ @event PipelineRunnerPool#run:start\n+ @type {ComputationResult}\n+*/\n+\n+/**\n+ * PipelineRunner's pipeline has halted\n+ * on its pipeline\n+ *\n+ * @event PipelineRunnerPool#run:end\n+ * @type {ComputationResult}\n+ */\n+\n+/**\n+ queue has instantiated on a runId.\n+\n+ @event PipelineRunnerPool#queue:start\n+ @type {string} runId\n+*/\n+\n+/**\n+ * queue has completed on a runId, and been deferenced.\n+ *\n+ * @event PipelineRunnerPool#queue:end\n+ * @type {string} runId\n+ */\n+\n+/**\n+ * a run has completed, and another one will begin. the queue\n+ * will not proceed until a run completes where the pipeline\n+ * is no longer in progress\n+ *\n+ * @event PipelineRunnerPool#pipeline:inProgress\n+ * @type {string} runId\n+ */\n+\n+/**\n+ * a new listener has been created for the specified db name\n+ *\n+ * @event PipelineRunnerPool#listener:created\n+ * @type {string} dbName\n+ */\n+\n+/**\n+ @abstract\n+ @protected\n+ @method createNewRunner\n+ @description generates env specific PipelineRunner.  called when no\n+ PipelineRunner exists\n+ @param {ComputationResult} result\n+ @param {function} cb\n+ @returns {undefined}\n+*/\n+PipelineRunnerPool.prototype.createNewRunner = function(result, cb) {\n+    const err = new ReferenceError('abstract createNewRunner must be extended');\n+    cb(err);\n+};\n+\n+/**\n+ * @method destroy\n+ * @description destroy pool.  scraps dbs in the registry (not the remote copies),\n+ * and all DBListeners\n+ * @param {function} cb\n+ * @returns {undefined}\n+ */\n+PipelineRunnerPool.prototype.destroy = function(cb) {\n+    async.series([\n+        (cb) => {\n+            if (!this._hasAttemptedToInitialize) {\n+                return cb(new Error('cannot destroy uninitalized pool'));\n+            } else if (!this._hasInitialized) {\n+                this.on('ready', () => cb());\n+            } else {\n+                cb();\n+            }\n+        },\n+        (cb) => this.dbRegistry.destroy(cb),\n+        (cb) => {\n+            this.events.removeAllListeners();\n+            /* istanbul ignore else */\n+            if (this.consortiaListener) {\n+                this.consortiaListener.destroy();\n+            }\n+            values(this.resultsListeners).forEach(listener => listener.destroy());\n+            Pouchy.PouchDB.removeAllListeners();\n+            cb();\n+        }\n+    ], cb);\n+};\n+\n+/**\n+ * @method getDecentralizedComputation\n+ * @description gets a DecentralizedComputation instance from the registry\n+ * @param {string} id computations store id\n+ * @param {function} cb\n+ * @returns {undefined}\n+ */\n+PipelineRunnerPool.prototype.getDecentralizedComputation = function(id, cb) {\n+    this.dbRegistry.get('computations').get(id)\n+    .then((comp) => {\n+        return this.computationRegistry.get(comp.name, comp.version);\n+    })\n+    .then((dComp) => cb(null, dComp))\n+    .catch(cb);\n+};\n+\n+/**\n+ * @private\n+ * @method _initConsortiumListeners\n+ * @description inits listeners to react to changed ComputationResult doc events\n+ * @param {function} cb\n+ * @returns {undefined}\n+ */\n+PipelineRunnerPool.prototype._initConsortiumListeners = function(cb) {\n+    this.dbRegistry.get('consortiameta').all((err, docs) => {\n+        /* istanbul ignore next */\n+        if (err) { return cb(err); }\n+        this.listenToConsortia(docs, cb);\n+    });\n+};\n+\n+/**\n+ * @method handleResultChange\n+ * @description instantiates or selects a PipelineRunner corresponding to the\n+ * new changed ComputationResult. runs the PipelineRunner with the new documents\n+ * @param {ComputationResult} result\n+ */\n+PipelineRunnerPool.prototype.handleResultChange = function(result) {\n+    if (!(result instanceof ComputationResult)) {\n+        throw new ReferenceError('expected ComputationResult instance');\n+    }\n+\n+    /* istanbul ignore next */\n+    if (result.data && result.data.complete) {\n+        this.emit('computation:complete', result.runId);\n+        if (this.handleRunComplete) { this.handleRunComplete(result); }\n+        return;\n+    }\n+    if (!this.runners[result.runId]) {\n+        return this.createNewRunner(result, (err, runner) => {\n+            // if we can't build a runner, diehard!\n+            /* istanbul ignore next */\n+            if (err) { throw err; }\n+            this.runners[result.runId] = runner;\n+            this.triggerRunner(result);\n+        });\n+    } else {\n+        this.triggerRunner(result);\n+    }\n+};\n+\n+/**\n+ * @abstract\n+ * @method _handleCreatedDB\n+ */\n+PipelineRunnerPool.prototype._handleCreatedDB = function() {\n+    throw new ReferenceError([\n+        'abstract db created/destroyed handlers must be extended.',\n+        'db', arguments[0], 'created or destroyed'\n+    ].join(' '));\n+};\n+\n+/**\n+ * @method init\n+ * @description kickoff Pool activity\n+ * @param {function=} cb\n+ * @returns {undefined}\n+ */\n+PipelineRunnerPool.prototype.init = function(cb) {\n+    this._hasAttemptedToInitialize = true;\n+    this._handleCreatedDestroyedDBs();\n+\n+    // open up DB conn eventing begins immediately\n+    async.series([\n+        (cb) => this._syncDB('consortiameta', cb),\n+        (cb) => this._syncDB('computations', cb),\n+        (cb) => this._initConsortiumListeners(cb),\n+        (cb) => this._listenForNewConsortia(cb)\n+    ], (err) => {\n+        /* istanbul ignore next */\n+        if (err) { return cb(err); }\n+        this._hasInitialized = true;\n+        this.emit('ready');\n+        if (cb) { cb(); }\n+    });\n+};\n+\n+/**\n+ * @method _listenForNewConsortia\n+ * @description\n+ */\n+PipelineRunnerPool.prototype._listenForNewConsortia = function(cb) {\n+    this.consortiaListener = new DBListener(this.dbRegistry.get('consortiameta'));\n+    this.consortiaListener.on('change', result => {\n+        this.listenToConsortia(result.doc);\n+    });\n+    cb();\n+};\n+\n+/**\n+ * @abstract\n+ * @method listenToConsortia\n+ * @description setup listeners on each consortium as required for\n+ * the env (e.g. local env, remote env)\n+ * @param {array|object} consortia raw consortia object(s)\n+ * @param {function} cb\n+ * @returns {undefined}\n+ */\n+PipelineRunnerPool.prototype.listenToConsortia = function(consortia, cb) {\n+    /* istanbul ignore next */\n+    if (!Array.isArray(consortia)) { consortia = [consortia]; }\n+    consortia = consortia.map(tium => new Consortium(tium));\n+    /* istanbul ignore else */\n+    if (!consortia.length) { return cb(); }\n+    return async.each(\n+        consortia,\n+        (consortium, _cb) => this._listenToConsortium(consortium, _cb),\n+        cb\n+    );\n+};\n+\n+/**\n+ * @private\n+ * @method _listenToConsortium\n+ * @description configures consortium listener for remote _or_ local store.\n+ * exists to help `listenToConsortia`\n+ * @param {Consortium} consortium\n+ * @param {function} cb\n+ * @returns {undefined}\n+ */\n+PipelineRunnerPool.prototype._listenToConsortium = function(consortium, cb) {\n+    let db;\n+    const ResultDocType = this.listenToRemote ?\n+        RemoteComputationResult :\n+        LocalComputationResult;\n+    if (this.listenToRemote) {\n+        db = this.dbRegistry.get('remote-consortium-' + consortium._id);\n+    } else if (this.listenToLocal) {\n+        db = this.dbRegistry.get('local-consortium-' + consortium._id);\n+    /* istanbul ignore else */\n+    } else {\n+        /* istanbul ignore next */\n+        return cb(new ReferenceError([\n+            'PipelineRunnerPool must listen to database events on local or',\n+            'remote databases.  see `.listenToLocal` & `.listenToRemote`'\n+        ].join(' ')));\n+    }\n+    /* istanbul ignore next */\n+    if (typeof this.resultsListeners[db.name] === DBListener) {\n+        return cb(\n+            new ReferenceError(`listener already exists for ${db.name}`)\n+        );\n+    }\n+    const listener = new DBListener(db);\n+    listener.on(\n+        'change',\n+        result => {\n+            this.handleResultChange(new ResultDocType(result.doc));\n+        }\n+    );\n+    this.resultsListeners[db.name] = listener;\n+    this.emit('listener:created', db.name);\n+    return cb(null, listener);\n+};\n+\n+/**\n+ * @method upsertListener\n+ * @description generates a DBListener for a results DB, provided the prefix\n+ * for that results db (e.g. local, remote), and the full db string as passed\n+ * by couch/pouch\n+ * @param {object} opts\n+ * @param {string} opts.listenToPrefix [local, remote] specified by child class\n+ *                                     if we are listening to a remote or local db\n+ * @param {string} opts.dbStr full db string as passed by couch/pouch on new db\n+ *                            event\n+ */\n+PipelineRunnerPool.prototype.upsertListener = function(opts) {\n+    const dbStr = opts.dbStr;\n+    const prefix = opts.listenToPrefix;\n+    const doListenRE = new RegExp(prefix + '-consortium-([^-]+)$');\n+    if (!dbStr.match(doListenRE)) { return; }\n+    const _id = dbStr.match(doListenRE)[1];\n+    this.dbRegistry.get('consortiameta').all()\n+    .then(consortia => {\n+        // PouchDBs created/destroyed events are broken.\n+        // Early detect if a DBListener is created (or in processes)\n+        // @ref https://github.com/pouchdb/pouchdb/issues/4922\n+        if (!consortia.length) { return; }\n+        if (this.resultsListeners[prefix + '-consortium-' + _id]) {\n+            return;\n+        } else {\n+            this.resultsListeners[prefix + '-consortium-' + _id] = 'constructing';\n+        }\n+        const consortium = consortia.find(tium => tium._id.match(_id));\n+        this.listenToConsortia(consortium);\n+    });\n+};\n+\n+/**\n+ * @private\n+ * @method _syncDB\n+ * @description synchronizes a db with the remote master.  some dbs\n+ * are restricted to up/ down/ only--these will not work.  although\n+ * dbs will auto-sync if configured, on init we sync some DBs fully\n+ * before completing initialization\n+ * @param {string} dbname\n+ * @param {function} db\n+ * @returns {undefined}\n+ */\n+PipelineRunnerPool.prototype._syncDB = function(dbname, cb) {\n+    const db = this.dbRegistry.get(dbname);\n+    const handleLikelySynced = () => {\n+        db.syncEmitter.removeListener('hasLikelySynced', handleLikelySynced);\n+        db.syncEmitter.removeListener('error', handleSyncError);\n+        cb();\n+    };\n+    const handleSyncError = (err) => {\n+        db.syncEmitter.removeListener('hasLikelySynced', handleSyncError);\n+        db.syncEmitter.removeListener('error', handleSyncError);\n+        cb(err);\n+    };\n+    if (db.hasLikelySynced) { return handleLikelySynced(); }\n+    db.syncEmitter.on('hasLikelySynced', handleLikelySynced);\n+    db.syncEmitter.on('error', handleSyncError);\n+\n+    this[dbname] = db;\n+};\n+\n+/**\n+ * @method triggerRunner\n+ * @description triggers a PipelineRunner to react to new result document.\n+ * maintains a queue, permitting only a single pipeline to run at a time\n+ * within a specific runId group. e.g. two new local results will may queue, and\n+ * the PipelineRunner will run on each of them _in series_\n+ * @fires PipelineRunnerPool#run:start\n+ * @fires PipelineRunnerPool#run:end\n+ * @fires PipelineRunnerPool#queue:start\n+ * @fires PipelineRunnerPool#queue:end\n+ * @fires PipelineRunnerPool#pipeline:inProgress\n+ * @param {ComputationResult} result\n+ * @returns {undefined}\n+ */\n+PipelineRunnerPool.prototype.triggerRunner = function(result) {","path":"src/models/pipeline/runner/pool/pipeline-runner-pool.js","position":418,"original_position":420,"commit_id":"930c7dc70358ffd2db282d309e5233e72caae6a5","original_commit_id":"6c205edd79f488be048087aa5a560046cbf09729","user":{"login":"swashcap","id":1858316,"avatar_url":"https://avatars.githubusercontent.com/u/1858316?v=3","gravatar_id":"","url":"https://api.github.com/users/swashcap","html_url":"https://github.com/swashcap","followers_url":"https://api.github.com/users/swashcap/followers","following_url":"https://api.github.com/users/swashcap/following{/other_user}","gists_url":"https://api.github.com/users/swashcap/gists{/gist_id}","starred_url":"https://api.github.com/users/swashcap/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/swashcap/subscriptions","organizations_url":"https://api.github.com/users/swashcap/orgs","repos_url":"https://api.github.com/users/swashcap/repos","events_url":"https://api.github.com/users/swashcap/events{/privacy}","received_events_url":"https://api.github.com/users/swashcap/received_events","type":"User","site_admin":false},"body":"Still don’t find the code to be clear, but whatever. As long as it works. :+1:","created_at":"2016-03-07T22:04:58Z","updated_at":"2016-03-07T22:04:58Z","html_url":"https://github.com/MRN-Code/coinstac-common/pull/19#discussion_r55280948","pull_request_url":"https://api.github.com/repos/MRN-Code/coinstac-common/pulls/19","_links":{"self":{"href":"https://api.github.com/repos/MRN-Code/coinstac-common/pulls/comments/55280948"},"html":{"href":"https://github.com/MRN-Code/coinstac-common/pull/19#discussion_r55280948"},"pull_request":{"href":"https://api.github.com/repos/MRN-Code/coinstac-common/pulls/19"}}},"pull_request":{"url":"https://api.github.com/repos/MRN-Code/coinstac-common/pulls/19","id":61186410,"html_url":"https://github.com/MRN-Code/coinstac-common/pull/19","diff_url":"https://github.com/MRN-Code/coinstac-common/pull/19.diff","patch_url":"https://github.com/MRN-Code/coinstac-common/pull/19.patch","issue_url":"https://api.github.com/repos/MRN-Code/coinstac-common/issues/19","number":19,"state":"open","locked":false,"title":"Feature/pipline runner pool","user":{"login":"cdaringe","id":1003261,"avatar_url":"https://avatars.githubusercontent.com/u/1003261?v=3","gravatar_id":"","url":"https://api.github.com/users/cdaringe","html_url":"https://github.com/cdaringe","followers_url":"https://api.github.com/users/cdaringe/followers","following_url":"https://api.github.com/users/cdaringe/following{/other_user}","gists_url":"https://api.github.com/users/cdaringe/gists{/gist_id}","starred_url":"https://api.github.com/users/cdaringe/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cdaringe/subscriptions","organizations_url":"https://api.github.com/users/cdaringe/orgs","repos_url":"https://api.github.com/users/cdaringe/repos","events_url":"https://api.github.com/users/cdaringe/events{/privacy}","received_events_url":"https://api.github.com/users/cdaringe/received_events","type":"User","site_admin":false},"body":"- [x] `cwd` ==> `Computation`, removed from `PipelineRunner`\r\n\r\n#### asana\r\nhttps://app.asana.com/0/45796090191289/95930724321667\r\n\r\n# problem statement\r\n- we need pooling support for minimal db events\r\n- we need pipeline runners who will have the responsibility of reacting to those events, collecting inputs for pipelines, running the pipelines, persisting the result & pipeline state, and letting the pooler know when the pipeline has halted\r\n\r\n# solution\r\n- do ^^^\r\n- fix too many bugs/incomplete designs\r\n- write a gazillion tests\r\n\r\n# discussion\r\n- dont lose it when you see how much content there is!  many of the files are symmetric, and have rather straight-forward interfaces.  those that look a little hairy all have docs\r\n- celebrate coverage!\r\n\r\n<img width=\"761\" alt=\"screen shot 2016-02-29 at 7 12 51 pm\" src=\"https://cloud.githubusercontent.com/assets/1003261/13416602/536dd6f8-df21-11e5-80c4-43e7eb5f74af.png\">\r\n","created_at":"2016-03-01T03:16:41Z","updated_at":"2016-03-07T22:04:58Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c47adeb349297ac8f25f84748f51efe10d4c166f","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/MRN-Code/coinstac-common/pulls/19/commits","review_comments_url":"https://api.github.com/repos/MRN-Code/coinstac-common/pulls/19/comments","review_comment_url":"https://api.github.com/repos/MRN-Code/coinstac-common/pulls/comments{/number}","comments_url":"https://api.github.com/repos/MRN-Code/coinstac-common/issues/19/comments","statuses_url":"https://api.github.com/repos/MRN-Code/coinstac-common/statuses/930c7dc70358ffd2db282d309e5233e72caae6a5","head":{"label":"MRN-Code:feature/pipline-runner-pool","ref":"feature/pipline-runner-pool","sha":"930c7dc70358ffd2db282d309e5233e72caae6a5","user":{"login":"MRN-Code","id":5330818,"avatar_url":"https://avatars.githubusercontent.com/u/5330818?v=3","gravatar_id":"","url":"https://api.github.com/users/MRN-Code","html_url":"https://github.com/MRN-Code","followers_url":"https://api.github.com/users/MRN-Code/followers","following_url":"https://api.github.com/users/MRN-Code/following{/other_user}","gists_url":"https://api.github.com/users/MRN-Code/gists{/gist_id}","starred_url":"https://api.github.com/users/MRN-Code/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MRN-Code/subscriptions","organizations_url":"https://api.github.com/users/MRN-Code/orgs","repos_url":"https://api.github.com/users/MRN-Code/repos","events_url":"https://api.github.com/users/MRN-Code/events{/privacy}","received_events_url":"https://api.github.com/users/MRN-Code/received_events","type":"Organization","site_admin":false},"repo":{"id":51021937,"name":"coinstac-common","full_name":"MRN-Code/coinstac-common","owner":{"login":"MRN-Code","id":5330818,"avatar_url":"https://avatars.githubusercontent.com/u/5330818?v=3","gravatar_id":"","url":"https://api.github.com/users/MRN-Code","html_url":"https://github.com/MRN-Code","followers_url":"https://api.github.com/users/MRN-Code/followers","following_url":"https://api.github.com/users/MRN-Code/following{/other_user}","gists_url":"https://api.github.com/users/MRN-Code/gists{/gist_id}","starred_url":"https://api.github.com/users/MRN-Code/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MRN-Code/subscriptions","organizations_url":"https://api.github.com/users/MRN-Code/orgs","repos_url":"https://api.github.com/users/MRN-Code/repos","events_url":"https://api.github.com/users/MRN-Code/events{/privacy}","received_events_url":"https://api.github.com/users/MRN-Code/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/MRN-Code/coinstac-common","description":"common models and utilities shared across the coinstac ecosystem","fork":false,"url":"https://api.github.com/repos/MRN-Code/coinstac-common","forks_url":"https://api.github.com/repos/MRN-Code/coinstac-common/forks","keys_url":"https://api.github.com/repos/MRN-Code/coinstac-common/keys{/key_id}","collaborators_url":"https://api.github.com/repos/MRN-Code/coinstac-common/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/MRN-Code/coinstac-common/teams","hooks_url":"https://api.github.com/repos/MRN-Code/coinstac-common/hooks","issue_events_url":"https://api.github.com/repos/MRN-Code/coinstac-common/issues/events{/number}","events_url":"https://api.github.com/repos/MRN-Code/coinstac-common/events","assignees_url":"https://api.github.com/repos/MRN-Code/coinstac-common/assignees{/user}","branches_url":"https://api.github.com/repos/MRN-Code/coinstac-common/branches{/branch}","tags_url":"https://api.github.com/repos/MRN-Code/coinstac-common/tags","blobs_url":"https://api.github.com/repos/MRN-Code/coinstac-common/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/MRN-Code/coinstac-common/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/MRN-Code/coinstac-common/git/refs{/sha}","trees_url":"https://api.github.com/repos/MRN-Code/coinstac-common/git/trees{/sha}","statuses_url":"https://api.github.com/repos/MRN-Code/coinstac-common/statuses/{sha}","languages_url":"https://api.github.com/repos/MRN-Code/coinstac-common/languages","stargazers_url":"https://api.github.com/repos/MRN-Code/coinstac-common/stargazers","contributors_url":"https://api.github.com/repos/MRN-Code/coinstac-common/contributors","subscribers_url":"https://api.github.com/repos/MRN-Code/coinstac-common/subscribers","subscription_url":"https://api.github.com/repos/MRN-Code/coinstac-common/subscription","commits_url":"https://api.github.com/repos/MRN-Code/coinstac-common/commits{/sha}","git_commits_url":"https://api.github.com/repos/MRN-Code/coinstac-common/git/commits{/sha}","comments_url":"https://api.github.com/repos/MRN-Code/coinstac-common/comments{/number}","issue_comment_url":"https://api.github.com/repos/MRN-Code/coinstac-common/issues/comments{/number}","contents_url":"https://api.github.com/repos/MRN-Code/coinstac-common/contents/{+path}","compare_url":"https://api.github.com/repos/MRN-Code/coinstac-common/compare/{base}...{head}","merges_url":"https://api.github.com/repos/MRN-Code/coinstac-common/merges","archive_url":"https://api.github.com/repos/MRN-Code/coinstac-common/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/MRN-Code/coinstac-common/downloads","issues_url":"https://api.github.com/repos/MRN-Code/coinstac-common/issues{/number}","pulls_url":"https://api.github.com/repos/MRN-Code/coinstac-common/pulls{/number}","milestones_url":"https://api.github.com/repos/MRN-Code/coinstac-common/milestones{/number}","notifications_url":"https://api.github.com/repos/MRN-Code/coinstac-common/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/MRN-Code/coinstac-common/labels{/name}","releases_url":"https://api.github.com/repos/MRN-Code/coinstac-common/releases{/id}","deployments_url":"https://api.github.com/repos/MRN-Code/coinstac-common/deployments","created_at":"2016-02-03T18:44:42Z","updated_at":"2016-02-15T18:42:02Z","pushed_at":"2016-03-07T20:30:26Z","git_url":"git://github.com/MRN-Code/coinstac-common.git","ssh_url":"git@github.com:MRN-Code/coinstac-common.git","clone_url":"https://github.com/MRN-Code/coinstac-common.git","svn_url":"https://github.com/MRN-Code/coinstac-common","homepage":"http://mrn-code.github.io/coinstac-common/","size":744,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"MRN-Code:master","ref":"master","sha":"cc751a36f799b5465f3eacd8b194fd5372b2f20c","user":{"login":"MRN-Code","id":5330818,"avatar_url":"https://avatars.githubusercontent.com/u/5330818?v=3","gravatar_id":"","url":"https://api.github.com/users/MRN-Code","html_url":"https://github.com/MRN-Code","followers_url":"https://api.github.com/users/MRN-Code/followers","following_url":"https://api.github.com/users/MRN-Code/following{/other_user}","gists_url":"https://api.github.com/users/MRN-Code/gists{/gist_id}","starred_url":"https://api.github.com/users/MRN-Code/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MRN-Code/subscriptions","organizations_url":"https://api.github.com/users/MRN-Code/orgs","repos_url":"https://api.github.com/users/MRN-Code/repos","events_url":"https://api.github.com/users/MRN-Code/events{/privacy}","received_events_url":"https://api.github.com/users/MRN-Code/received_events","type":"Organization","site_admin":false},"repo":{"id":51021937,"name":"coinstac-common","full_name":"MRN-Code/coinstac-common","owner":{"login":"MRN-Code","id":5330818,"avatar_url":"https://avatars.githubusercontent.com/u/5330818?v=3","gravatar_id":"","url":"https://api.github.com/users/MRN-Code","html_url":"https://github.com/MRN-Code","followers_url":"https://api.github.com/users/MRN-Code/followers","following_url":"https://api.github.com/users/MRN-Code/following{/other_user}","gists_url":"https://api.github.com/users/MRN-Code/gists{/gist_id}","starred_url":"https://api.github.com/users/MRN-Code/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MRN-Code/subscriptions","organizations_url":"https://api.github.com/users/MRN-Code/orgs","repos_url":"https://api.github.com/users/MRN-Code/repos","events_url":"https://api.github.com/users/MRN-Code/events{/privacy}","received_events_url":"https://api.github.com/users/MRN-Code/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/MRN-Code/coinstac-common","description":"common models and utilities shared across the coinstac ecosystem","fork":false,"url":"https://api.github.com/repos/MRN-Code/coinstac-common","forks_url":"https://api.github.com/repos/MRN-Code/coinstac-common/forks","keys_url":"https://api.github.com/repos/MRN-Code/coinstac-common/keys{/key_id}","collaborators_url":"https://api.github.com/repos/MRN-Code/coinstac-common/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/MRN-Code/coinstac-common/teams","hooks_url":"https://api.github.com/repos/MRN-Code/coinstac-common/hooks","issue_events_url":"https://api.github.com/repos/MRN-Code/coinstac-common/issues/events{/number}","events_url":"https://api.github.com/repos/MRN-Code/coinstac-common/events","assignees_url":"https://api.github.com/repos/MRN-Code/coinstac-common/assignees{/user}","branches_url":"https://api.github.com/repos/MRN-Code/coinstac-common/branches{/branch}","tags_url":"https://api.github.com/repos/MRN-Code/coinstac-common/tags","blobs_url":"https://api.github.com/repos/MRN-Code/coinstac-common/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/MRN-Code/coinstac-common/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/MRN-Code/coinstac-common/git/refs{/sha}","trees_url":"https://api.github.com/repos/MRN-Code/coinstac-common/git/trees{/sha}","statuses_url":"https://api.github.com/repos/MRN-Code/coinstac-common/statuses/{sha}","languages_url":"https://api.github.com/repos/MRN-Code/coinstac-common/languages","stargazers_url":"https://api.github.com/repos/MRN-Code/coinstac-common/stargazers","contributors_url":"https://api.github.com/repos/MRN-Code/coinstac-common/contributors","subscribers_url":"https://api.github.com/repos/MRN-Code/coinstac-common/subscribers","subscription_url":"https://api.github.com/repos/MRN-Code/coinstac-common/subscription","commits_url":"https://api.github.com/repos/MRN-Code/coinstac-common/commits{/sha}","git_commits_url":"https://api.github.com/repos/MRN-Code/coinstac-common/git/commits{/sha}","comments_url":"https://api.github.com/repos/MRN-Code/coinstac-common/comments{/number}","issue_comment_url":"https://api.github.com/repos/MRN-Code/coinstac-common/issues/comments{/number}","contents_url":"https://api.github.com/repos/MRN-Code/coinstac-common/contents/{+path}","compare_url":"https://api.github.com/repos/MRN-Code/coinstac-common/compare/{base}...{head}","merges_url":"https://api.github.com/repos/MRN-Code/coinstac-common/merges","archive_url":"https://api.github.com/repos/MRN-Code/coinstac-common/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/MRN-Code/coinstac-common/downloads","issues_url":"https://api.github.com/repos/MRN-Code/coinstac-common/issues{/number}","pulls_url":"https://api.github.com/repos/MRN-Code/coinstac-common/pulls{/number}","milestones_url":"https://api.github.com/repos/MRN-Code/coinstac-common/milestones{/number}","notifications_url":"https://api.github.com/repos/MRN-Code/coinstac-common/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/MRN-Code/coinstac-common/labels{/name}","releases_url":"https://api.github.com/repos/MRN-Code/coinstac-common/releases{/id}","deployments_url":"https://api.github.com/repos/MRN-Code/coinstac-common/deployments","created_at":"2016-02-03T18:44:42Z","updated_at":"2016-02-15T18:42:02Z","pushed_at":"2016-03-07T20:30:26Z","git_url":"git://github.com/MRN-Code/coinstac-common.git","ssh_url":"git@github.com:MRN-Code/coinstac-common.git","clone_url":"https://github.com/MRN-Code/coinstac-common.git","svn_url":"https://github.com/MRN-Code/coinstac-common","homepage":"http://mrn-code.github.io/coinstac-common/","size":744,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/MRN-Code/coinstac-common/pulls/19"},"html":{"href":"https://github.com/MRN-Code/coinstac-common/pull/19"},"issue":{"href":"https://api.github.com/repos/MRN-Code/coinstac-common/issues/19"},"comments":{"href":"https://api.github.com/repos/MRN-Code/coinstac-common/issues/19/comments"},"review_comments":{"href":"https://api.github.com/repos/MRN-Code/coinstac-common/pulls/19/comments"},"review_comment":{"href":"https://api.github.com/repos/MRN-Code/coinstac-common/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/MRN-Code/coinstac-common/pulls/19/commits"},"statuses":{"href":"https://api.github.com/repos/MRN-Code/coinstac-common/statuses/930c7dc70358ffd2db282d309e5233e72caae6a5"}}}}