payload: {"action":"created","comment":{"url":"https://api.github.com/repos/PolymerLabs/arcs/pulls/comments/324441089","pull_request_review_id":288339313,"id":324441089,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDMyNDQ0MTA4OQ==","diff_hunk":"@@ -0,0 +1,598 @@\n+/**\n+ * @license\n+ * Copyright (c) 2019 Google Inc. All rights reserved.\n+ * This code may only be used under the BSD style license found at\n+ * http://polymer.github.io/LICENSE.txt\n+ * Code distributed by Google as part of this project is also\n+ * subject to an additional IP rights grant found at\n+ * http://polymer.github.io/PATENTS.txt\n+ */\n+\n+import {CRDTSingletonTypeRecord, SingletonOperation, SingletonOpTypes, CRDTSingleton, SingletonOperationSet, SingletonOperationClear} from '../crdt/crdt-singleton.js';\n+import {CRDTCollectionTypeRecord, Referenceable, CollectionOpTypes, CollectionOperation, CRDTCollection, CollectionOperationAdd, CollectionOperationRemove} from '../crdt/crdt-collection.js';\n+import {ActiveStore, ProxyCallback, ProxyMessage, ProxyMessageType, StorageMode} from './store.js';\n+import {BackingStore} from './backing-store.js';\n+import {CRDTEntityTypeRecord, CRDTEntity, EntityData} from '../crdt/crdt-entity.js';\n+import {Dictionary} from 'lodash';\n+import {DirectStore} from './direct-store.js';\n+import {StorageKey} from './storage-key.js';\n+import {VersionMap, CRDTModel, CRDTTypeRecord} from '../crdt/crdt.js';\n+import {Exists} from './drivers/driver-factory.js';\n+import {Type} from '../type.js';\n+import {Producer, Consumer, Runnable} from '../hot.js';\n+\n+type HoldRecord = {\n+  ids: Set<string>,\n+  onRelease: Runnable\n+};\n+\n+export type Reference = {id: string, storageKey: StorageKey};\n+export class ReferenceCollection extends CRDTCollection<Reference> {}\n+export class ReferenceSingleton extends CRDTSingleton<Reference> {} \n+\n+export type ReferenceModeOperation<T extends Referenceable> = CRDTSingletonTypeRecord<T>['operation'] | CRDTCollectionTypeRecord<T>['operation'];\n+\n+type PreEnqueuedMessage<Container extends CRDTTypeRecord, Entity extends CRDTTypeRecord, RefContainer extends CRDTTypeRecord> = \n+  {from: 'proxy', message: ProxyMessage<Container>} |\n+  {from: 'backing', message: ProxyMessage<Entity>, muxId: string} |\n+  {from: 'container', message: ProxyMessage<RefContainer>};\n+type EnqueuedMessage<Container extends CRDTTypeRecord, Entity extends CRDTTypeRecord, RefContainer extends CRDTTypeRecord> = \n+  PreEnqueuedMessage<Container, Entity, RefContainer> & {promise: Consumer<boolean>};\n+\n+type BlockableRunnable = {fn: Runnable, block?: string};\n+\n+/**\n+ * ReferenceModeStores adapt between a collection (CRDTCollection or CRDTSingleton) of entities from the perspective of their public API,\n+ * and a collection of references + a backing store of entity CRDTs from an internal storage perspective.\n+ * \n+ * ReferenceModeStores maintain a queue of incoming updates (the receiveQueue) and process them one at a time. When possible, the results\n+ * of this processing are immediately sent upwards (to connected StorageProxies) and downwards (to storage). However, there are a few \n+ * caveats:\n+ * - incoming operations and models from StorageProxies may require several writes to storage - one for each modified entity, and one\n+ *   to the container store. These are processed serially, so that a container doesn't get updated if backing store modifications fail.\n+ * - updates from the container store need to be blocked on ensuring the required data is also available in the backing store.\n+ *   The holdQueue ensures that these blocks are tracked and processed appropriately.\n+ * - updates should always be sent in order, so a blocked send should block subsequent sends too. The pendingSends queue ensures that all\n+ *   outgoing updates are sent in the correct order.\n+ * \n+ */\n+export class ReferenceModeStore<Entity extends Referenceable, S extends Dictionary<Referenceable>, C extends Dictionary<Referenceable>, \n+                                ReferenceContainer extends CRDTSingletonTypeRecord<Reference> | CRDTCollectionTypeRecord<Reference>,\n+                                Container extends CRDTSingletonTypeRecord<Entity> | CRDTCollectionTypeRecord<Entity>> extends ActiveStore<Container> {\n+\n+  /*\n+   * The underlying backing store and container store that this reference view is built from\n+   */\n+  backingStore: BackingStore<CRDTEntityTypeRecord<S, C>>;\n+  containerStore: DirectStore<ReferenceContainer>;\n+\n+  /*\n+   * Registered callbacks to Storage Proxies\n+   */\n+  private callbacks: Map<number, ProxyCallback<Container>> = new Map();\n+  private nextCallbackID = 1;\n+\n+  /*\n+   * A randomly generated key that is used for synthesized entity CRDT modifications.\n+   *\n+   * When entity updates are recieved by instances of ReferenceModeStore, they're non-CRDT blobs of data.\n+   * The ReferenceModeStore needs to convert them to tracked CRDTs, which means it needs to synthesize\n+   * updates. This key is used as the unique write key for those updates.\n+   */\n+  private crdtKey = (Math.random() * Math.pow(2,64)) + '';\n+\n+  /*\n+   * The versions dictionary tracks the maximum write version for each entity ID, to ensure synthesized\n+   * updates can be correctly applied downstream.\n+   */\n+  private versions: Dictionary<Dictionary<number>> = {};\n+\n+  /*\n+   * A queue of incoming updates from the backing store, container store, and connected proxies.\n+   * These are dealt wit atomically, to avoid transient states where an operation has only been partially\n+   * processed (e.g. backing written but container update not written).\n+   */\n+  private receiveQueue: EnqueuedMessage<Container, CRDTEntityTypeRecord<S, C>, ReferenceContainer>[] = [];\n+\n+  /*\n+   * A queue of send Runnables. Some of these may be blocked on entities becoming available in the\n+  * backing store.\n+   */\n+  private pendingSends: BlockableRunnable[] = [];\n+\n+  /*\n+   * A queue of blocks to the pendingSends queue.\n+   */\n+  private holdQueue: HoldQueue = new HoldQueue();\n+\n+  static async construct<Entity extends Referenceable, S extends Dictionary<Referenceable>, C extends Dictionary<Referenceable>,\n+                         ReferenceContainer extends CRDTSingletonTypeRecord<Reference> | CRDTCollectionTypeRecord<Reference>,\n+                         Container extends CRDTSingletonTypeRecord<Entity> | CRDTCollectionTypeRecord<Entity>>(\n+      backingKey: StorageKey, storageKey: StorageKey, exists: Exists, type: Type, backingConstructor: new () => CRDTModel<CRDTEntityTypeRecord<S, C>>,\n+      referenceConstructor: new () => CRDTModel<ReferenceContainer>, modelConstructor: new () => CRDTModel<Container>) {\n+    const result = new ReferenceModeStore<Entity, S, C, ReferenceContainer, Container>(storageKey, exists, type, StorageMode.ReferenceMode, modelConstructor);\n+    result.backingStore = await BackingStore.construct(backingKey, exists, type, StorageMode.Backing, backingConstructor);\n+    result.containerStore = await DirectStore.construct(storageKey, exists, type, StorageMode.Direct, referenceConstructor);\n+    result.registerStoreCallbacks();\n+    return result;\n+  }\n+\n+  on(callback: ProxyCallback<Container>): number {\n+    const id = this.nextCallbackID++;\n+    this.callbacks.set(id, callback);\n+    return id;\n+  }\n+  off(callback: number) {\n+    this.callbacks.delete(callback);\n+  }\n+\n+  private registerStoreCallbacks() {\n+    this.backingStore.on(this.onBackingStore.bind(this));\n+    this.containerStore.on(this.onContainerStore.bind(this));\n+  }\n+\n+  /**\n+   * Messages are enqueued onto an object-wide queue and processed in order. \n+   * Internally, each handler (handleContainerStore, handleBackingStore, handleProxyMessage)\n+   * should not return until the response relevant to the message has been received.\n+   * \n+   * When handling proxy messages, this implies 2 rounds of update - first the backing\n+   * store needs to be updated, and once that has completed then the container store needs\n+   * to be updated.\n+   */\n+  async onContainerStore(message: ProxyMessage<ReferenceContainer>) {\n+    return this.enqueue({from: 'container', message});\n+  }\n+\n+  async onBackingStore(message: ProxyMessage<CRDTEntityTypeRecord<S, C>>, muxId: string) {\n+    return this.enqueue({from: 'backing', message, muxId});\n+  }\n+\n+  async onProxyMessage(message: ProxyMessage<Container>): Promise<boolean> {\n+    return this.enqueue({from: 'proxy', message});\n+  }\n+\n+  /**\n+   * enqueue an incoming update onto the object-wide queue and return a promise that will be resolved\n+   * when the update is processed.\n+   */\n+  private async enqueue(entry: PreEnqueuedMessage<Container, CRDTEntityTypeRecord<S, C>, ReferenceContainer>): Promise<boolean> {\n+    return new Promise((resolve, reject) => {\n+      const startProcessing = this.receiveQueue.length === 0;\n+      this.receiveQueue.push({...entry, promise: resolve});\n+      if (startProcessing) {\n+        void this.processQueue();\n+      }\n+    });\n+  }\n+\n+  private async processQueue() {\n+    while (this.receiveQueue.length > 0) {\n+      const nextMessage = this.receiveQueue[0];\n+      console.log('pQ processing', nextMessage);\n+      switch (nextMessage.from) {\n+        case 'proxy':\n+          nextMessage.promise(await this.handleProxyMessage(nextMessage.message));\n+          break;\n+        case 'backing':\n+          nextMessage.promise(await this.handleBackingStore(nextMessage.message, nextMessage.muxId));\n+          break;\n+        case 'container':\n+          nextMessage.promise(await this.handleContainerStore(nextMessage.message));\n+          break;\n+        default:\n+          throw new Error('invalid message type');\n+      }\n+      this.receiveQueue.shift();\n+    }\n+  }\n+\n+  /**\n+   * Handle an update from the container store.\n+   * \n+   * Operations and Models either enqueue an immediate send (if all referenced entities\n+   * are available in the backing store) or enqueue a blocked send (if some referenced\n+   * entities are not yet present).\n+   * \n+   * Note that the blocking mechanism isn't version-aware, so removes followed by\n+   * adds may not correctly sync. If this turns out to be a problem then we can\n+   * add version information to references and update the blocking store to gate\n+   * on a version as well as presence.\n+   * \n+   * Sync requests are propagated upwards to the storage proxy.\n+   */\n+  private async handleContainerStore(message: ProxyMessage<ReferenceContainer>) {\n+    switch (message.type) {\n+      case ProxyMessageType.Operations: {\n+        for (const operation of message.operations) {\n+          const reference = this.operationElement<Reference>(operation);\n+          let getEntity;\n+\n+          if (reference) {\n+            const entityCRDT = this.backingStore.getLocalModel(reference.id);            \n+            if (!entityCRDT) {\n+              this.enqueueBlockingSend([reference.id], () => {\n+                const entityCRDT = this.backingStore.getLocalModel(reference.id);\n+                const getEntity = () => this.entityFromModel(entityCRDT.getData(), reference.id);\n+                const upstreamOp = this.updateOp<Reference, Entity>(operation, getEntity);\n+                void this.send({type: ProxyMessageType.Operations, operations: [upstreamOp]});      \n+              });\n+              break;\n+            }\n+            getEntity = () => this.entityFromModel(entityCRDT.getData(), reference.id);\n+          } else {\n+            getEntity = () => null;\n+          }\n+\n+          this.enqueueSend(() => {\n+            const upstreamOp = this.updateOp<Reference, Entity>(operation, getEntity);\n+            void this.send({type: ProxyMessageType.Operations, operations: [upstreamOp]});\n+          });\n+        }\n+        break;\n+      }\n+      case ProxyMessageType.ModelUpdate: {\n+        const data = message.model;\n+        const {pendingIds, model} = this.constructPendingIdsAndModel(data);\n+\n+        const send = () => void this.send({type: ProxyMessageType.ModelUpdate, model: model()});\n+\n+        if (pendingIds.length === 0) {\n+          this.enqueueSend(send);\n+        } else {\n+          this.enqueueBlockingSend(pendingIds, send);\n+        }\n+        break;\n+      }\n+      case ProxyMessageType.SyncRequest: {\n+        this.enqueueSend(() => {\n+          void this.send({type: ProxyMessageType.SyncRequest});\n+        });\n+        break;\n+      }\n+      default: {\n+        throw new Error('Unexpected ProxyMessageType');\n+      }\n+    }\n+    return true;\n+  }\n+\n+  /**\n+   * Handle an update from the backing store.\n+   * \n+   * Model and Operation updates are routed directly to the holdQueue, where they may unblock\n+   * pending sends but will not have any other action.\n+   * \n+   * Syncs should never occur as operation/model updates to the backing store are generated\n+   * by this ReferenceModeStore object and hence should never be out-of-order.\n+   */\n+  private async handleBackingStore(message: ProxyMessage<CRDTEntityTypeRecord<S, C>>, muxId: string) {\n+    switch (message.type) {\n+      case ProxyMessageType.ModelUpdate:\n+      case ProxyMessageType.Operations:\n+        this.holdQueue.processID(muxId);\n+        break;\n+      case ProxyMessageType.SyncRequest:\n+        throw new Error('Unexpected SyncRequest from backing store');\n+      default:\n+        throw new Error('Unexpected ProxyMessageType');\n+    }\n+    return true;\n+  }\n+\n+  /**\n+   * Handle an update from an upstream StorageProxy.\n+   * \n+   * Model and Operation updates apply first to the backing store, then to the container store.\n+   * Backing store updates should never fail as updates are locally generated.\n+   * For Operations:\n+   * - If the container store update has succeeds, then the update is mirrored to non-sending StorageProxies.\n+   * - If the container store update fails, then a `false` return value ensures that the upstream proxy\n+   *   will request a sync.\n+   * Model updates should not fail.\n+   * \n+   * Sync requests are handled by directly constructing and sending a model \n+   */\n+  private async handleProxyMessage(message: ProxyMessage<Container>): Promise<boolean> {\n+    switch (message.type) {\n+      case ProxyMessageType.Operations: {\n+        const operations = message.operations;\n+        for (const operation of operations) {\n+          const entity = this.operationElement<Entity>(operation);\n+          let reference = null;\n+          if (entity) {\n+            await this.updateBackingStore(entity);\n+            reference = {id: entity.id, storageKey: this.backingStore.storageKey};\n+          }\n+          const containerMessage = this.updateOp<Entity, Reference>(operation, () => reference);\n+          const response = await this.containerStore.onProxyMessage({type: ProxyMessageType.Operations, operations: [containerMessage], id: 1});\n+          if (response) {\n+            this.enqueueSend(() => void this.sendExcept(message, message.id));\n+          } else {\n+            return false;\n+          }\n+        }\n+        break;\n+      }\n+      case ProxyMessageType.ModelUpdate: {\n+        const {version, values} = message.model;\n+        const newValues = {} as Dictionary<{value: Reference, version: VersionMap}>;\n+        const backingStoreReceipts: Promise<boolean>[] = [];\n+        Object.keys(values).forEach(id => {\n+          newValues[id] = {value: {id, storageKey: this.backingStore.storageKey}, version: values[id].version};\n+          backingStoreReceipts.push(this.updateBackingStore(values[id].value));\n+        });\n+        await Promise.all(backingStoreReceipts);\n+        const model = {version, values: newValues};\n+        await this.containerStore.onProxyMessage({type: ProxyMessageType.ModelUpdate, model, id: 1});\n+        this.enqueueSend(() => this.sendExcept(message, message.id));\n+        break;\n+      }\n+      case ProxyMessageType.SyncRequest: {\n+        const {pendingIds, model} = this.constructPendingIdsAndModel(this.containerStore.localModel.getData());\n+        const send = () => void this.callbacks.get(message.id)({type: ProxyMessageType.ModelUpdate, model: model(), id: message.id});\n+        if (pendingIds.length === 0) {\n+          this.enqueueSend(send);\n+        } else {\n+          this.enqueueBlockingSend(pendingIds, send);\n+        }\n+        \n+        break;\n+      }\n+      default:\n+          throw new Error('Unexpected ProxyMessageType');\n+    }\n+    return true;\n+  }\n+\n+  /**\n+   * Enqueues a sending function on the send queue. If the send queue is empty then\n+   * the function is immediately invoked.\n+   */\n+  private enqueueSend(runnable: Runnable) {\n+    if (this.pendingSends.length === 0) {\n+      runnable();\n+    } else {\n+      this.pendingSends.push({fn: runnable});\n+    }\n+  }\n+\n+  /**\n+   * Enqueues a send function on the send queue, deferring execution until the\n+   * provided id list is available in the backing store.\n+   */\n+  private enqueueBlockingSend(ids: string[], runnable: Runnable) {\n+    const block = Math.random() + '';\n+    this.pendingSends.push({fn: runnable, block});\n+    this.holdQueue.enqueue(ids, () => this.processPendingSends(block));\n+  }\n+\n+  /**\n+   * Process any sends in the pending send queue, including sends blocked on the\n+   * provided block. This should only be called by the holdQueue.\n+   */\n+  private processPendingSends(block: string) {\n+    while (this.pendingSends.length > 0) {\n+      if (this.pendingSends[0].block == null || this.pendingSends[0].block === block) {","path":"src/runtime/storageNG/reference-mode-store.ts","position":381,"original_position":376,"commit_id":"fa68e6f0ce6e897f795faf8b2b9bd4329aba8670","original_commit_id":"fc8ff786eabb3a1373eb07477f8a88695f6c2eac","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":"I'm following lint practice here - == null, === everything else","created_at":"2019-09-14T23:27:44Z","updated_at":"2019-09-14T23:27:44Z","html_url":"https://github.com/PolymerLabs/arcs/pull/3523#discussion_r324441089","pull_request_url":"https://api.github.com/repos/PolymerLabs/arcs/pulls/3523","author_association":"COLLABORATOR","_links":{"self":{"href":"https://api.github.com/repos/PolymerLabs/arcs/pulls/comments/324441089"},"html":{"href":"https://github.com/PolymerLabs/arcs/pull/3523#discussion_r324441089"},"pull_request":{"href":"https://api.github.com/repos/PolymerLabs/arcs/pulls/3523"}},"in_reply_to_id":321567699},"pull_request":{"url":"https://api.github.com/repos/PolymerLabs/arcs/pulls/3523","id":313859519,"node_id":"MDExOlB1bGxSZXF1ZXN0MzEzODU5NTE5","html_url":"https://github.com/PolymerLabs/arcs/pull/3523","diff_url":"https://github.com/PolymerLabs/arcs/pull/3523.diff","patch_url":"https://github.com/PolymerLabs/arcs/pull/3523.patch","issue_url":"https://api.github.com/repos/PolymerLabs/arcs/issues/3523","number":3523,"state":"open","locked":false,"title":"[WIP] Reference mode store","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":"","created_at":"2019-09-04T05:04:20Z","updated_at":"2019-09-14T23:27:44Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"requested_reviewers":[{"login":"galganif","id":4754402,"node_id":"MDQ6VXNlcjQ3NTQ0MDI=","avatar_url":"https://avatars2.githubusercontent.com/u/4754402?v=4","gravatar_id":"","url":"https://api.github.com/users/galganif","html_url":"https://github.com/galganif","followers_url":"https://api.github.com/users/galganif/followers","following_url":"https://api.github.com/users/galganif/following{/other_user}","gists_url":"https://api.github.com/users/galganif/gists{/gist_id}","starred_url":"https://api.github.com/users/galganif/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/galganif/subscriptions","organizations_url":"https://api.github.com/users/galganif/orgs","repos_url":"https://api.github.com/users/galganif/repos","events_url":"https://api.github.com/users/galganif/events{/privacy}","received_events_url":"https://api.github.com/users/galganif/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/PolymerLabs/arcs/pulls/3523/commits","review_comments_url":"https://api.github.com/repos/PolymerLabs/arcs/pulls/3523/comments","review_comment_url":"https://api.github.com/repos/PolymerLabs/arcs/pulls/comments{/number}","comments_url":"https://api.github.com/repos/PolymerLabs/arcs/issues/3523/comments","statuses_url":"https://api.github.com/repos/PolymerLabs/arcs/statuses/fa68e6f0ce6e897f795faf8b2b9bd4329aba8670","head":{"label":"shans:referenceModeStore","ref":"referenceModeStore","sha":"fa68e6f0ce6e897f795faf8b2b9bd4329aba8670","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},"repo":{"id":157310114,"node_id":"MDEwOlJlcG9zaXRvcnkxNTczMTAxMTQ=","name":"arcs","full_name":"shans/arcs","private":false,"owner":{"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},"html_url":"https://github.com/shans/arcs","description":"Arcs","fork":true,"url":"https://api.github.com/repos/shans/arcs","forks_url":"https://api.github.com/repos/shans/arcs/forks","keys_url":"https://api.github.com/repos/shans/arcs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/shans/arcs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/shans/arcs/teams","hooks_url":"https://api.github.com/repos/shans/arcs/hooks","issue_events_url":"https://api.github.com/repos/shans/arcs/issues/events{/number}","events_url":"https://api.github.com/repos/shans/arcs/events","assignees_url":"https://api.github.com/repos/shans/arcs/assignees{/user}","branches_url":"https://api.github.com/repos/shans/arcs/branches{/branch}","tags_url":"https://api.github.com/repos/shans/arcs/tags","blobs_url":"https://api.github.com/repos/shans/arcs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/shans/arcs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/shans/arcs/git/refs{/sha}","trees_url":"https://api.github.com/repos/shans/arcs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/shans/arcs/statuses/{sha}","languages_url":"https://api.github.com/repos/shans/arcs/languages","stargazers_url":"https://api.github.com/repos/shans/arcs/stargazers","contributors_url":"https://api.github.com/repos/shans/arcs/contributors","subscribers_url":"https://api.github.com/repos/shans/arcs/subscribers","subscription_url":"https://api.github.com/repos/shans/arcs/subscription","commits_url":"https://api.github.com/repos/shans/arcs/commits{/sha}","git_commits_url":"https://api.github.com/repos/shans/arcs/git/commits{/sha}","comments_url":"https://api.github.com/repos/shans/arcs/comments{/number}","issue_comment_url":"https://api.github.com/repos/shans/arcs/issues/comments{/number}","contents_url":"https://api.github.com/repos/shans/arcs/contents/{+path}","compare_url":"https://api.github.com/repos/shans/arcs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/shans/arcs/merges","archive_url":"https://api.github.com/repos/shans/arcs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/shans/arcs/downloads","issues_url":"https://api.github.com/repos/shans/arcs/issues{/number}","pulls_url":"https://api.github.com/repos/shans/arcs/pulls{/number}","milestones_url":"https://api.github.com/repos/shans/arcs/milestones{/number}","notifications_url":"https://api.github.com/repos/shans/arcs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/shans/arcs/labels{/name}","releases_url":"https://api.github.com/repos/shans/arcs/releases{/id}","deployments_url":"https://api.github.com/repos/shans/arcs/deployments","created_at":"2018-11-13T02:45:40Z","updated_at":"2018-11-13T02:45:45Z","pushed_at":"2019-09-14T19:25:59Z","git_url":"git://github.com/shans/arcs.git","ssh_url":"git@github.com:shans/arcs.git","clone_url":"https://github.com/shans/arcs.git","svn_url":"https://github.com/shans/arcs","homepage":"","size":63983,"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,"disabled":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"PolymerLabs:master","ref":"master","sha":"fec334976fea38e322dc00ce688f65fbd3b80ca9","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","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":"2019-09-13T21:59:45Z","pushed_at":"2019-09-14T19:26:01Z","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":65710,"stargazers_count":43,"watchers_count":43,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":18,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":242,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":18,"open_issues":242,"watchers":43,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/PolymerLabs/arcs/pulls/3523"},"html":{"href":"https://github.com/PolymerLabs/arcs/pull/3523"},"issue":{"href":"https://api.github.com/repos/PolymerLabs/arcs/issues/3523"},"comments":{"href":"https://api.github.com/repos/PolymerLabs/arcs/issues/3523/comments"},"review_comments":{"href":"https://api.github.com/repos/PolymerLabs/arcs/pulls/3523/comments"},"review_comment":{"href":"https://api.github.com/repos/PolymerLabs/arcs/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/PolymerLabs/arcs/pulls/3523/commits"},"statuses":{"href":"https://api.github.com/repos/PolymerLabs/arcs/statuses/fa68e6f0ce6e897f795faf8b2b9bd4329aba8670"}},"author_association":"COLLABORATOR"}}
other: {"actor":{"display_login":"shans"}}