{"action":"created","comment":{"url":"https://api.github.com/repos/PolymerLabs/arcs/pulls/comments/220421634","pull_request_review_id":158779837,"id":220421634,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIyMDQyMTYzNA==","diff_hunk":"@@ -0,0 +1,364 @@\n+import {CrdtCollectionModel, Model} from '../crdt-collection-model.js';\n+import {Tracing} from '../../../../tracelib/trace.js';\n+import {assert} from '../../../../platform/assert-web.js';\n+import {PouchDbStorageProvider} from \"./pouch-db-storage-provider.js\";\n+import {Type} from \"../../type.js\";\n+import {PouchDbStorage} from \"./pouch-db-storage\";\n+\n+\n+/**\n+ * Defines a callback interface to allow for modifying a\n+ * CrdtCollectionModel during a get/modify/set cycle.\n+ */ \n+interface CrdtCollectionModelMutator {\n+  (crdt: CrdtCollectionModel) : CrdtCollectionModel;\n+}\n+\n+export class PouchDbCollection extends PouchDbStorageProvider {\n+  /** The local synced model */\n+  private _model: CrdtCollectionModel;  // NOTE: Private, but outside code accesses this :(\n+\n+  /**\n+   * Create a new PouchDbCollection.\n+   *\n+   * @param type the underlying type for this collection.\n+   * @param storageEngine a reference back to the PouchDbStorage, used for baseStorageKey calls.\n+   * @param name appears unused.\n+   * @param id see base class.\n+   * @param key the storage key for this collection.\n+   */\n+  constructor(type: Type, storageEngine: PouchDbStorage, name: string, id: string, key: string) {\n+    super(type, storageEngine, name, id, key);\n+\n+    this._model = new CrdtCollectionModel();\n+    assert(this.version !== null);\n+  }\n+\n+  /** @inheritDoc */\n+  backingType() {\n+    return this.type.primitiveType();\n+  }\n+\n+  // TODO(lindner): appears unused\n+  // clone() {\n+  //   const handle = new PouchDbCollection(this.type, this.storageEngine, this.name, this.id, null);\n+  //   handle.cloneFrom(this);\n+  //   return handle;\n+  // }\n+\n+  async cloneFrom(handle) {\n+    this.referenceMode = handle.referenceMode;\n+    const literal = await handle.toLiteral();\n+    if (this.referenceMode && literal.model.length > 0) {\n+      await Promise.all([this.ensureBackingStore(), handle.ensureBackingStore()]);\n+      literal.model = literal.model.map(({id, value}) => ({id, value: {id: value.id, storageKey: this.backingStore.storageKey}}));\n+      const underlying = await handle.backingStore.getMultiple(literal.model.map(({id}) => id));\n+      await this.backingStore.storeMultiple(underlying, [this.storageKey]);\n+    }\n+    this.fromLiteral(literal);\n+  }\n+\n+  /** @inheritDoc */\n+  async modelForSynchronization() {\n+    // TODO(lindner): should this change for reference mode??\n+    return {\n+      version: this.version,\n+      model: await this._toList()\n+    };\n+  }\n+\n+  /** @inheritDoc */\n+  // Returns {version, model: [{id, value, keys: []}]}\n+  // TODO(lindner): this is async, but the base class isn't....\n+  async toLiteral() {\n+    return {\n+      version: this.version,\n+      model: (await this.getModel()).toLiteral()\n+    };\n+  }\n+\n+  /**\n+   * Populate this collection with a provided version/model\n+   */\n+  fromLiteral({version, model}) {\n+    this.version = version;\n+    // TODO(lindner): this might not be initialized yet...\n+    this._model = new CrdtCollectionModel(model);\n+  }\n+\n+  async _toList() {\n+    if (this.referenceMode) {\n+      const items = (await this.getModel()).toLiteral();\n+      if (items.length === 0) {\n+        return [];\n+      }\n+      const refSet = new Set();\n+      items.forEach(item => refSet.add(item.value.storageKey));\n+      assert(refSet.size === 1, `multiple storageKeys in reference set of collection not yet supported.`);\n+      const ref = refSet.values().next().value;\n+\n+      await this.ensureBackingStore();\n+\n+      const retrieveItem = async item => {\n+        const ref = item.value;\n+        return {id: ref.id, value: await this.backingStore.get(ref.id), keys: item.keys};\n+      };\n+\n+      return await Promise.all(items.map(retrieveItem));\n+    }\n+    return (await this.getModel()).toLiteral();\n+  }\n+\n+  async toList() {\n+    return (await this._toList()).map(item => item.value);\n+  }\n+\n+  /**\n+   * Returns an array of values for each of the specified ids.\n+   *\n+   * @param ids items to fetch from the underlying CRDT model.\n+   * @return an array of values from the underlying CRDT\n+   */\n+  async getMultiple(ids: string[]) {\n+    assert(!this.referenceMode, \"getMultiple not implemented for referenceMode stores\");\n+    const model = await this.getModel();\n+    return ids.map(id => model.getValue(id));\n+  }\n+\n+  /**\n+   * Store multiple values with the given keys in the Collection.\n+   * TODO(lindner): document originatorId, which is unused.\n+   */\n+  async storeMultiple(values, keys, originatorId=null) {\n+    assert(!this.referenceMode, \"storeMultiple not implemented for referenceMode stores\");\n+\n+    this.getModelAndUpdate((crdtmodel) => {\n+      values.map(value => crdtmodel.add(value.id, value, keys));\n+      return crdtmodel;\n+    });\n+  }\n+\n+\n+  /**\n+   * Get a specific Id value previously set using store(). \n+   *\n+   * @remarks Note that the id referred to here is not the same as\n+   * used in the constructor.\n+   */\n+  async get(id) {\n+    if (this.referenceMode) {\n+      const ref = (await this.getModel()).getValue(id);\n+      if (ref == null) {\n+        return null;\n+      }\n+      await this.ensureBackingStore();\n+      const result = await this.backingStore.get(ref.id);\n+      return result;\n+    }\n+    \n+    const model = await this.getModel();\n+    return model.getValue(id);\n+  }\n+\n+  /**\n+   * Store the specific value to the collection.  Value must include an id entry.\n+   *\n+   * @param value A data object with an id entry that is used as a key.\n+   * @param keys The CRDT keys used to store this object\n+   * @param orginatorId TBD passed to event listeners\n+   */\n+  async store(value, keys: string[], originatorId=null) : Promise<void> {\n+    assert(keys != null && keys.length > 0, 'keys required');\n+    const id = value.id;\n+    \n+    const changeEvent = {value, keys, effective: undefined};\n+\n+    if (this.referenceMode) {\n+      const referredType = this.type.primitiveType();\n+      const storageKey = this.storageEngine.baseStorageKey(referredType, this.storageKey);\n+\n+      // Update the referred data\n+      await this.getModelAndUpdate((crdtmodel) => {\n+        changeEvent.effective = crdtmodel.add(value.id, {id: value.id, storageKey}, keys);\n+        return crdtmodel;\n+      });\n+\n+      await this.ensureBackingStore();\n+      await this.backingStore.store(value, keys);\n+    } else {\n+      await this.getModelAndUpdate((crdtmodel) => {\n+        // check for existing keys?\n+        changeEvent.effective = crdtmodel.add(value.id, value, keys);\n+        return crdtmodel;\n+      });\n+    }\n+\n+    this.version++;\n+\n+    // 2. Notify Listeners\n+    this._fire('change', {add: [changeEvent], version: this.version, originatorId});\n+  }\n+\n+\n+  /**\n+   * Remove ids from a collection for specific keys.\n+   * @param id the id to remove.\n+   * @param keys the CRDT specific keys to remove.\n+   * @param originatorId TBD passed to event listeners.\n+   */\n+  async remove(id, keys:string[] = [], originatorId=null) {\n+\n+    await this.getModelAndUpdate((crdtmodel) => {\n+      if (keys.length === 0) {\n+        keys = crdtmodel.getKeys(id);\n+      }\n+      const value = crdtmodel.getValue(id);\n+\n+      if (value !== null) {\n+        const effective = crdtmodel.remove(id, keys);\n+        // TODO(lindner): isolate side effects...\n+        this.version++;\n+        this._fire('change', {remove: [{value, keys, effective}], version: this.version, originatorId});\n+      }\n+      return crdtmodel;\n+    });\n+  }\n+\n+  /**\n+   * Triggered when the storage key has been modified.  For now we\n+   * just refetch and trigger listeners.  This is fast since the data\n+   * is synced locally.\n+   */\n+  public onRemoteStateSynced() {\n+    // updates internal state\n+    this.getModel();\n+  }\n+\n+  /**\n+   * Updates the local model cache from PouchDB and returns the CRDT\n+   * model for use.\n+   */\n+  private async getModel(): Promise<CrdtCollectionModel> {\n+    try {\n+      const result = await this.db.get(this.pouchDbKey.location);\n+      \n+      // compare revisions\n+      if (this._rev !== result._rev) {\n+        // remote revision is different, update local copy.\n+        this._model = new CrdtCollectionModel(result['model']);\n+        this._rev = result._rev;\n+        this.version++;  // yuck.\n+        // TODO(lindner): fire change events here?\n+      }\n+\n+    } catch (err) {\n+      this._model = new CrdtCollectionModel();\n+      this._rev = undefined;\n+      // TODO(lindner): throw\n+    }\n+    return this._model;\n+  }\n+\n+\n+  /**\n+   * Provides a way to apply changes to the model in a way that will result in the\n+   * crdt being written to the underlying PouchDB.\n+   * \n+   * - A new entry is stored if it doesn't exists.\n+   * - If the existing entry is available it is fetched and the\n+   *   internal state is updated.\n+   * - A copy of the CRDT model is passed to the modelMutator, which may change it.\n+   * - If the model is mutated by `modelMutator`, write a new revision and update the local\n+   *   cached copy.\n+   *\n+   * @param modelMutator allows for modifying a copy of the underlying crdt model.\n+   */\n+  private async getModelAndUpdate(modelMutator: CrdtCollectionModelMutator): Promise<CrdtCollectionModel> {\n+    // Keep retrying the operation until it succeeds.\n+    while (1) {\n+      let doc;\n+      //: PouchDB.Core.IdMeta & PouchDB.Core.GetMeta & Model & {referenceMode: boolean, type: {}};\n+\n+      let notFound = false;\n+      try {\n+        doc = await this.db.get(this.pouchDbKey.location);\n+        // as PouchDB.Core.IdMeta & PouchDB.Core.GetMeta & Model & {referenceMode: boolean, type: };\n+\n+        // Check remote doc.\n+        // TODO(lindner): refactor with getModel above.\n+        if (this._rev !== doc._rev) {\n+          // remote revision is different, update local copy.\n+          this._model = new CrdtCollectionModel(doc['model']);\n+          this._rev = doc._rev;\n+          this.version++;  // yuck.\n+          // TODO(lindner): fire change events here?","path":"runtime/ts/storage/pouchdb/pouch-db-collection.ts","position":294,"original_position":294,"commit_id":"194ebd6bb116e8d99ff75d4451422f040e2d31dc","original_commit_id":"194ebd6bb116e8d99ff75d4451422f040e2d31dc","user":{"login":"shans","id":329053,"node_id":"MDQ6VXNlcjMyOTA1Mw==","avatar_url":"https://avatars2.githubusercontent.com/u/329053?v=4","gravatar_id":"","url":"https://api.github.com/users/shans","html_url":"https://github.com/shans","followers_url":"https://api.github.com/users/shans/followers","following_url":"https://api.github.com/users/shans/following{/other_user}","gists_url":"https://api.github.com/users/shans/gists{/gist_id}","starred_url":"https://api.github.com/users/shans/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shans/subscriptions","organizations_url":"https://api.github.com/users/shans/orgs","repos_url":"https://api.github.com/users/shans/repos","events_url":"https://api.github.com/users/shans/events{/privacy}","received_events_url":"https://api.github.com/users/shans/received_events","type":"User","site_admin":false},"body":"Yup, need to diff the old model and the new one and send the difference as update events.","created_at":"2018-09-26T04:09:54Z","updated_at":"2018-09-26T04:15:42Z","html_url":"https://github.com/PolymerLabs/arcs/pull/1972#discussion_r220421634","pull_request_url":"https://api.github.com/repos/PolymerLabs/arcs/pulls/1972","author_association":"COLLABORATOR","_links":{"self":{"href":"https://api.github.com/repos/PolymerLabs/arcs/pulls/comments/220421634"},"html":{"href":"https://github.com/PolymerLabs/arcs/pull/1972#discussion_r220421634"},"pull_request":{"href":"https://api.github.com/repos/PolymerLabs/arcs/pulls/1972"}}},"pull_request":{"url":"https://api.github.com/repos/PolymerLabs/arcs/pulls/1972","id":218142338,"node_id":"MDExOlB1bGxSZXF1ZXN0MjE4MTQyMzM4","html_url":"https://github.com/PolymerLabs/arcs/pull/1972","diff_url":"https://github.com/PolymerLabs/arcs/pull/1972.diff","patch_url":"https://github.com/PolymerLabs/arcs/pull/1972.patch","issue_url":"https://api.github.com/repos/PolymerLabs/arcs/issues/1972","number":1972,"state":"open","locked":false,"title":"PouchDb support for Arcs Storage","user":{"login":"lindner","id":96715,"node_id":"MDQ6VXNlcjk2NzE1","avatar_url":"https://avatars1.githubusercontent.com/u/96715?v=4","gravatar_id":"","url":"https://api.github.com/users/lindner","html_url":"https://github.com/lindner","followers_url":"https://api.github.com/users/lindner/followers","following_url":"https://api.github.com/users/lindner/following{/other_user}","gists_url":"https://api.github.com/users/lindner/gists{/gist_id}","starred_url":"https://api.github.com/users/lindner/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lindner/subscriptions","organizations_url":"https://api.github.com/users/lindner/orgs","repos_url":"https://api.github.com/users/lindner/repos","events_url":"https://api.github.com/users/lindner/events{/privacy}","received_events_url":"https://api.github.com/users/lindner/received_events","type":"User","site_admin":false},"body":"- Introduces pouchdb-adapter-memory at v7.0.0\r\n- Add a new manual test as pouch-db-tests.js\r\n- exports Model from CrdtCollectionModel, adds more detail on error messages\r\n- PouchDbStorage manages mapping from PouchDbKeys to actual database instances and replication.\r\n- PouchDbStorageProvider mimics the inmemory and firebase version of the same class.\r\n- PouchDbCollection supports reference mode and maintains a CrdtCollectionModel in Pouch.\r\n- PouchDbVariable introduces and maintains a new VariableStorage interface based on the _stored variable.\r\n- PouchDbBigCollection is unimplemented.\r\n- Adds some docs to StorageProviderBase\r\n- Adds PouchDbStorage support to StorageProviderFactory\r\n- Document shell query parameters to some extent.\r\n- Adds a build:typedoc target to help visualize datastructures\r\n\r\nThere are still ~29 TODOs in the code, and more to do.  You can test this manually\r\nby adding a storage and torageKeyBase query param. Here's an example of using indexeddb:\r\n\r\n   http://localhost:8080/shell/apps/web/index.html?storage=1&storageKeyBase=pouchdb://local/mydb\r\n\r\nAnd here's an example of using a remote server (which you can setup by following the instructions in server/README.md)\r\n\r\n   http://localhost:8080/shell/apps/web/index.html?storage=1&storageKeyBase=pouchdb://local/mydb","created_at":"2018-09-25T22:50:20Z","updated_at":"2018-09-26T04:15:42Z","closed_at":null,"merged_at":null,"merge_commit_sha":"e2da34243acdb0f6d1bcd05a5e09c4b94a125d29","assignee":null,"assignees":[],"requested_reviewers":[{"login":"cromwellian","id":142886,"node_id":"MDQ6VXNlcjE0Mjg4Ng==","avatar_url":"https://avatars2.githubusercontent.com/u/142886?v=4","gravatar_id":"","url":"https://api.github.com/users/cromwellian","html_url":"https://github.com/cromwellian","followers_url":"https://api.github.com/users/cromwellian/followers","following_url":"https://api.github.com/users/cromwellian/following{/other_user}","gists_url":"https://api.github.com/users/cromwellian/gists{/gist_id}","starred_url":"https://api.github.com/users/cromwellian/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cromwellian/subscriptions","organizations_url":"https://api.github.com/users/cromwellian/orgs","repos_url":"https://api.github.com/users/cromwellian/repos","events_url":"https://api.github.com/users/cromwellian/events{/privacy}","received_events_url":"https://api.github.com/users/cromwellian/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/PolymerLabs/arcs/pulls/1972/commits","review_comments_url":"https://api.github.com/repos/PolymerLabs/arcs/pulls/1972/comments","review_comment_url":"https://api.github.com/repos/PolymerLabs/arcs/pulls/comments{/number}","comments_url":"https://api.github.com/repos/PolymerLabs/arcs/issues/1972/comments","statuses_url":"https://api.github.com/repos/PolymerLabs/arcs/statuses/194ebd6bb116e8d99ff75d4451422f040e2d31dc","head":{"label":"lindner:storage-pouchdb8","ref":"storage-pouchdb8","sha":"194ebd6bb116e8d99ff75d4451422f040e2d31dc","user":{"login":"lindner","id":96715,"node_id":"MDQ6VXNlcjk2NzE1","avatar_url":"https://avatars1.githubusercontent.com/u/96715?v=4","gravatar_id":"","url":"https://api.github.com/users/lindner","html_url":"https://github.com/lindner","followers_url":"https://api.github.com/users/lindner/followers","following_url":"https://api.github.com/users/lindner/following{/other_user}","gists_url":"https://api.github.com/users/lindner/gists{/gist_id}","starred_url":"https://api.github.com/users/lindner/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lindner/subscriptions","organizations_url":"https://api.github.com/users/lindner/orgs","repos_url":"https://api.github.com/users/lindner/repos","events_url":"https://api.github.com/users/lindner/events{/privacy}","received_events_url":"https://api.github.com/users/lindner/received_events","type":"User","site_admin":false},"repo":{"id":135092571,"node_id":"MDEwOlJlcG9zaXRvcnkxMzUwOTI1NzE=","name":"arcs","full_name":"lindner/arcs","private":false,"owner":{"login":"lindner","id":96715,"node_id":"MDQ6VXNlcjk2NzE1","avatar_url":"https://avatars1.githubusercontent.com/u/96715?v=4","gravatar_id":"","url":"https://api.github.com/users/lindner","html_url":"https://github.com/lindner","followers_url":"https://api.github.com/users/lindner/followers","following_url":"https://api.github.com/users/lindner/following{/other_user}","gists_url":"https://api.github.com/users/lindner/gists{/gist_id}","starred_url":"https://api.github.com/users/lindner/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lindner/subscriptions","organizations_url":"https://api.github.com/users/lindner/orgs","repos_url":"https://api.github.com/users/lindner/repos","events_url":"https://api.github.com/users/lindner/events{/privacy}","received_events_url":"https://api.github.com/users/lindner/received_events","type":"User","site_admin":false},"html_url":"https://github.com/lindner/arcs","description":null,"fork":true,"url":"https://api.github.com/repos/lindner/arcs","forks_url":"https://api.github.com/repos/lindner/arcs/forks","keys_url":"https://api.github.com/repos/lindner/arcs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/lindner/arcs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/lindner/arcs/teams","hooks_url":"https://api.github.com/repos/lindner/arcs/hooks","issue_events_url":"https://api.github.com/repos/lindner/arcs/issues/events{/number}","events_url":"https://api.github.com/repos/lindner/arcs/events","assignees_url":"https://api.github.com/repos/lindner/arcs/assignees{/user}","branches_url":"https://api.github.com/repos/lindner/arcs/branches{/branch}","tags_url":"https://api.github.com/repos/lindner/arcs/tags","blobs_url":"https://api.github.com/repos/lindner/arcs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/lindner/arcs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/lindner/arcs/git/refs{/sha}","trees_url":"https://api.github.com/repos/lindner/arcs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/lindner/arcs/statuses/{sha}","languages_url":"https://api.github.com/repos/lindner/arcs/languages","stargazers_url":"https://api.github.com/repos/lindner/arcs/stargazers","contributors_url":"https://api.github.com/repos/lindner/arcs/contributors","subscribers_url":"https://api.github.com/repos/lindner/arcs/subscribers","subscription_url":"https://api.github.com/repos/lindner/arcs/subscription","commits_url":"https://api.github.com/repos/lindner/arcs/commits{/sha}","git_commits_url":"https://api.github.com/repos/lindner/arcs/git/commits{/sha}","comments_url":"https://api.github.com/repos/lindner/arcs/comments{/number}","issue_comment_url":"https://api.github.com/repos/lindner/arcs/issues/comments{/number}","contents_url":"https://api.github.com/repos/lindner/arcs/contents/{+path}","compare_url":"https://api.github.com/repos/lindner/arcs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/lindner/arcs/merges","archive_url":"https://api.github.com/repos/lindner/arcs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/lindner/arcs/downloads","issues_url":"https://api.github.com/repos/lindner/arcs/issues{/number}","pulls_url":"https://api.github.com/repos/lindner/arcs/pulls{/number}","milestones_url":"https://api.github.com/repos/lindner/arcs/milestones{/number}","notifications_url":"https://api.github.com/repos/lindner/arcs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/lindner/arcs/labels{/name}","releases_url":"https://api.github.com/repos/lindner/arcs/releases{/id}","deployments_url":"https://api.github.com/repos/lindner/arcs/deployments","created_at":"2018-05-28T00:44:14Z","updated_at":"2018-05-29T04:18:11Z","pushed_at":"2018-09-25T23:12:12Z","git_url":"git://github.com/lindner/arcs.git","ssh_url":"git@github.com:lindner/arcs.git","clone_url":"https://github.com/lindner/arcs.git","svn_url":"https://github.com/lindner/arcs","homepage":null,"size":28115,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"PolymerLabs:master","ref":"master","sha":"710aeb5a0a4ac7c611c32d8cca5dc9fc249a0741","user":{"login":"PolymerLabs","id":5912903,"node_id":"MDEyOk9yZ2FuaXphdGlvbjU5MTI5MDM=","avatar_url":"https://avatars2.githubusercontent.com/u/5912903?v=4","gravatar_id":"","url":"https://api.github.com/users/PolymerLabs","html_url":"https://github.com/PolymerLabs","followers_url":"https://api.github.com/users/PolymerLabs/followers","following_url":"https://api.github.com/users/PolymerLabs/following{/other_user}","gists_url":"https://api.github.com/users/PolymerLabs/gists{/gist_id}","starred_url":"https://api.github.com/users/PolymerLabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/PolymerLabs/subscriptions","organizations_url":"https://api.github.com/users/PolymerLabs/orgs","repos_url":"https://api.github.com/users/PolymerLabs/repos","events_url":"https://api.github.com/users/PolymerLabs/events{/privacy}","received_events_url":"https://api.github.com/users/PolymerLabs/received_events","type":"Organization","site_admin":false},"repo":{"id":80783122,"node_id":"MDEwOlJlcG9zaXRvcnk4MDc4MzEyMg==","name":"arcs","full_name":"PolymerLabs/arcs","private":false,"owner":{"login":"PolymerLabs","id":5912903,"node_id":"MDEyOk9yZ2FuaXphdGlvbjU5MTI5MDM=","avatar_url":"https://avatars2.githubusercontent.com/u/5912903?v=4","gravatar_id":"","url":"https://api.github.com/users/PolymerLabs","html_url":"https://github.com/PolymerLabs","followers_url":"https://api.github.com/users/PolymerLabs/followers","following_url":"https://api.github.com/users/PolymerLabs/following{/other_user}","gists_url":"https://api.github.com/users/PolymerLabs/gists{/gist_id}","starred_url":"https://api.github.com/users/PolymerLabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/PolymerLabs/subscriptions","organizations_url":"https://api.github.com/users/PolymerLabs/orgs","repos_url":"https://api.github.com/users/PolymerLabs/repos","events_url":"https://api.github.com/users/PolymerLabs/events{/privacy}","received_events_url":"https://api.github.com/users/PolymerLabs/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/PolymerLabs/arcs","description":"Arcs aims to create an open ecosystem for privacy-preserving, AI-first computing.","fork":false,"url":"https://api.github.com/repos/PolymerLabs/arcs","forks_url":"https://api.github.com/repos/PolymerLabs/arcs/forks","keys_url":"https://api.github.com/repos/PolymerLabs/arcs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/PolymerLabs/arcs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/PolymerLabs/arcs/teams","hooks_url":"https://api.github.com/repos/PolymerLabs/arcs/hooks","issue_events_url":"https://api.github.com/repos/PolymerLabs/arcs/issues/events{/number}","events_url":"https://api.github.com/repos/PolymerLabs/arcs/events","assignees_url":"https://api.github.com/repos/PolymerLabs/arcs/assignees{/user}","branches_url":"https://api.github.com/repos/PolymerLabs/arcs/branches{/branch}","tags_url":"https://api.github.com/repos/PolymerLabs/arcs/tags","blobs_url":"https://api.github.com/repos/PolymerLabs/arcs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/PolymerLabs/arcs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/PolymerLabs/arcs/git/refs{/sha}","trees_url":"https://api.github.com/repos/PolymerLabs/arcs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/PolymerLabs/arcs/statuses/{sha}","languages_url":"https://api.github.com/repos/PolymerLabs/arcs/languages","stargazers_url":"https://api.github.com/repos/PolymerLabs/arcs/stargazers","contributors_url":"https://api.github.com/repos/PolymerLabs/arcs/contributors","subscribers_url":"https://api.github.com/repos/PolymerLabs/arcs/subscribers","subscription_url":"https://api.github.com/repos/PolymerLabs/arcs/subscription","commits_url":"https://api.github.com/repos/PolymerLabs/arcs/commits{/sha}","git_commits_url":"https://api.github.com/repos/PolymerLabs/arcs/git/commits{/sha}","comments_url":"https://api.github.com/repos/PolymerLabs/arcs/comments{/number}","issue_comment_url":"https://api.github.com/repos/PolymerLabs/arcs/issues/comments{/number}","contents_url":"https://api.github.com/repos/PolymerLabs/arcs/contents/{+path}","compare_url":"https://api.github.com/repos/PolymerLabs/arcs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/PolymerLabs/arcs/merges","archive_url":"https://api.github.com/repos/PolymerLabs/arcs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/PolymerLabs/arcs/downloads","issues_url":"https://api.github.com/repos/PolymerLabs/arcs/issues{/number}","pulls_url":"https://api.github.com/repos/PolymerLabs/arcs/pulls{/number}","milestones_url":"https://api.github.com/repos/PolymerLabs/arcs/milestones{/number}","notifications_url":"https://api.github.com/repos/PolymerLabs/arcs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/PolymerLabs/arcs/labels{/name}","releases_url":"https://api.github.com/repos/PolymerLabs/arcs/releases{/id}","deployments_url":"https://api.github.com/repos/PolymerLabs/arcs/deployments","created_at":"2017-02-03T00:32:19Z","updated_at":"2018-09-26T04:01:19Z","pushed_at":"2018-09-26T04:03:49Z","git_url":"git://github.com/PolymerLabs/arcs.git","ssh_url":"git@github.com:PolymerLabs/arcs.git","clone_url":"https://github.com/PolymerLabs/arcs.git","svn_url":"https://github.com/PolymerLabs/arcs","homepage":"","size":27771,"stargazers_count":30,"watchers_count":30,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":22,"mirror_url":null,"archived":false,"open_issues_count":183,"license":{"key":"other","name":"Other","spdx_id":null,"url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":22,"open_issues":183,"watchers":30,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/PolymerLabs/arcs/pulls/1972"},"html":{"href":"https://github.com/PolymerLabs/arcs/pull/1972"},"issue":{"href":"https://api.github.com/repos/PolymerLabs/arcs/issues/1972"},"comments":{"href":"https://api.github.com/repos/PolymerLabs/arcs/issues/1972/comments"},"review_comments":{"href":"https://api.github.com/repos/PolymerLabs/arcs/pulls/1972/comments"},"review_comment":{"href":"https://api.github.com/repos/PolymerLabs/arcs/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/PolymerLabs/arcs/pulls/1972/commits"},"statuses":{"href":"https://api.github.com/repos/PolymerLabs/arcs/statuses/194ebd6bb116e8d99ff75d4451422f040e2d31dc"}},"author_association":"COLLABORATOR"}}
{"actor":{"display_login":"shans"}}