{"action":"created","comment":{"url":"https://api.github.com/repos/mixer/etcd3/pulls/comments/184728963","pull_request_review_id":115984090,"id":184728963,"diff_hunk":"@@ -0,0 +1,282 @@\n+import BigNumber from 'bignumber.js';\n+import * as EventEmitter from 'events';\n+import { EtcdNoLeaderError, EtcdNotLeaderError } from './errors';\n+import { Lease } from './lease';\n+import { Namespace } from './namespace';\n+\n+/**\n+ * Implmentation of etcd election.\n+ * @see https://github.com/coreos/etcd/blob/master/clientv3/concurrency/election.go\n+ *\n+ * @example\n+ * const client = new Etcd3()\n+ * const election = new Election(client, 'singleton_service')\n+ * const id = BigNumber.random().toString()\n+ *\n+ * // process will hang here until elected\n+ * await election.campaign(id)\n+ */\n+export class Election extends EventEmitter {\n+  public static readonly prefix = 'election';\n+\n+  private readonly namespace: Namespace;\n+  private lease: Lease;\n+\n+  private leaseId = '';\n+  private _leaderKey = '';\n+  private _leaderRevision = '';\n+  private _isCampaigning = false;\n+  private _isObserving = false;\n+\n+  public get leaderKey(): string { return this._leaderKey; }\n+  public get leaderRevision(): string { return this._leaderRevision; }\n+  public get isReady(): boolean { return this.leaseId.length > 0; }\n+  public get isCampaigning(): boolean { return this._isCampaigning; }\n+  public get isObserving(): boolean { return this._isObserving; }\n+\n+  constructor(public readonly parent: Namespace,\n+              public readonly name: string,\n+              public readonly ttl: number = 60) {\n+    super();\n+    this.namespace = parent.namespace(this.getPrefix());\n+    this.lease = this.namespace.lease(ttl);\n+    this.lease.on('lost', () => this.onLeaseLost());\n+  }\n+\n+  public on(event: 'leader', listener: (leaderKey: string) => void): this;\n+  public on(event: 'error', listener: (error: any) => void): this;\n+  public on(event: string|symbol, listener: Function): this;\n+  public on(event: string|symbol, listener: Function): this {\n+    super.on(event, listener);\n+    if (this.shouldObserve(event)) {\n+      this.tryObserve();\n+    }\n+    return this;\n+  }\n+\n+  /* istanbul ignore next */\n+  public addListener(event: string|symbol, listener: Function): this {\n+    return this.on(event, listener);\n+  }\n+\n+  /* istanbul ignore next */\n+  public once(event: string|symbol, listener: Function): this {\n+    super.once(event, listener);\n+    if (this.shouldObserve(event)) {\n+      this.tryObserve();\n+    }\n+    return this;\n+  }\n+\n+  public async ready() {\n+    this.leaseId = await this.lease.grant();\n+  }\n+\n+  public async campaign(value: string) {\n+    await this.ready();\n+\n+    const result = await this.namespace\n+      .if(this.leaseId, 'Create', '==', 0)\n+      .then(this.namespace.put(this.leaseId).value(value).lease(this.leaseId))\n+      .else(this.namespace.get(this.leaseId))\n+      .commit();\n+\n+    this._leaderKey = `${this.getPrefix()}${this.leaseId}`;\n+    this._leaderRevision = result.header.revision;\n+    this._isCampaigning = true;\n+\n+    if (!result.succeeded) {\n+      try {\n+        const kv = result.responses[0].response_range.kvs[0];\n+        this._leaderRevision = kv.create_revision;\n+        if (kv.value.toString() !== value) {\n+          await this.proclaim(value);\n+        }\n+      } catch (error) {\n+        await this.resign();\n+        return this.emitOrThrowError(error);\n+      }\n+    }\n+\n+    try {\n+      await this.waitForElected();\n+    } catch (error) {\n+      await this.resign();\n+      return this.emitOrThrowError(error);\n+    }\n+  }\n+\n+  public async proclaim(value: any) {\n+    if (!this._isCampaigning) {\n+      return this.emitOrThrowError(new EtcdNotLeaderError());\n+    }\n+\n+    const r = await this.namespace\n+      .if(this.leaseId, 'Create', '==', this._leaderRevision)\n+      .then(this.namespace.put(this.leaseId).value(value).lease(this.leaseId))\n+      .commit();\n+\n+    if (!r.succeeded) {\n+      this._leaderKey = '';\n+      return this.emitOrThrowError(new EtcdNotLeaderError());\n+    }\n+  }\n+\n+  public async resign() {\n+    if (!this.isCampaigning) {\n+      return;\n+    }\n+\n+    const r = await this.namespace\n+      .if(this.leaseId, 'Create', '==', this._leaderRevision)\n+      .then(this.namespace.delete().key(this.leaseId))\n+      .commit();\n+\n+    if (!r.succeeded) {\n+      // If fail, revoke lease for performing resigning\n+      await this.lease.revoke();\n+      this.lease = this.namespace.lease(this.ttl);\n+      this.lease.on('lost', () => this.onLeaseLost());\n+      this.leaseId = '';\n+    }\n+\n+    this._leaderKey = '';\n+    this._leaderRevision = '';\n+    this._isCampaigning = false;\n+  }\n+\n+  public async getLeader() {\n+    const result = await this.namespace.getAll().sort('Create', 'Ascend').keys();\n+    if (result.length === 0) {\n+      throw new EtcdNoLeaderError();\n+    }\n+    return `${this.getPrefix()}${result[0]}`;\n+  }\n+\n+  public getPrefix() {\n+    return `${Election.prefix}/${this.name}/`;\n+  }\n+\n+  private async waitForElected() {\n+    // find last create before this\n+    const lastRevision = new BigNumber(this.leaderRevision).minus(1).toString();\n+    const result = await this.namespace\n+      .getAll()\n+      .maxCreateRevision(lastRevision)\n+      .sort('Create', 'Descend')\n+      .keys();\n+\n+    // no one before this, elected\n+    if (result.length === 0) {\n+      return;\n+    }\n+\n+    // wait all keys created ealier are deleted\n+    await waitForDeletes(this.namespace, result);\n+  }\n+\n+  private async observe() {\n+    if (this._isObserving) {\n+      return;\n+    }\n+\n+    try {\n+      this._isObserving = true;\n+\n+      // looking for current leader\n+      let leaderKey = '';\n+      const result = await this.namespace.getAll().sort('Create', 'Ascend').keys();\n+\n+      if (result.length === 0) {\n+        // if not found, wait for leader\n+        const watcher = await this.parent.watch().prefix(this.getPrefix()).create();\n+        try {\n+          leaderKey = await new Promise<string>((resolve, reject) => {\n+            watcher.on('put', kv => resolve(kv.key.toString()));\n+            watcher.on('error', reject);\n+          });\n+        } finally {\n+          await watcher.cancel();\n+        }\n+      } else {\n+        leaderKey = `${this.getPrefix()}${result[0]}`;\n+      }\n+\n+      // emit current leader\n+      this.emit('leader', leaderKey);\n+\n+      // wait for delete event\n+      await waitForDelete(this.parent, leaderKey);\n+    } finally {\n+      this._isObserving = false;\n+    }\n+\n+    // only keep watch if listened\n+    if (this.listenerCount('leader') > 0) {\n+      this.tryObserve();\n+    }\n+  }\n+\n+  private tryObserve(): void {\n+    this.observe().catch(error => this.emitOrThrowError(error));\n+  }\n+\n+  private shouldObserve(event: string|symbol): boolean {\n+    switch (event) {\n+      case 'leader': return true;\n+      default: return false;\n+    }\n+  }\n+\n+  private emitOrThrowError(error: any): void {\n+    if (this.listenerCount('error') > 0) {\n+      this.emit('error', error);\n+    } else {\n+      throw error;\n+    }\n+  }\n+\n+  private onLeaseLost() {\n+    this.lease = this.namespace.lease(this.ttl);\n+    this.lease.on('lost', () => this.onLeaseLost());\n+    this.leaseId = '';\n+  }\n+}\n+\n+async function waitForDelete(namespace: Namespace, key: string) {\n+  const watcher = await namespace.watch().key(key).create();\n+  const deleteOrError = new Promise(async (resolve, reject) => {\n+    // waiting for deleting of that key\n+    watcher.on('delete', resolve);\n+    watcher.on('error', reject);\n+  });\n+\n+  try {\n+    await deleteOrError;\n+  } finally {\n+    await watcher.cancel();\n+  }\n+}\n+\n+async function waitForDeletes(namespace: Namespace, keys: string[]) {\n+  if (keys.length === 0) {\n+    return;\n+  } else if (keys.length === 1) {","path":"src/election.ts","position":264,"original_position":264,"commit_id":"7c3563facf52d62c0b368aa0ef24503c0ac09dd0","original_commit_id":"7c3563facf52d62c0b368aa0ef24503c0ac09dd0","user":{"login":"SimonSchick","id":3259089,"avatar_url":"https://avatars3.githubusercontent.com/u/3259089?v=4","gravatar_id":"","url":"https://api.github.com/users/SimonSchick","html_url":"https://github.com/SimonSchick","followers_url":"https://api.github.com/users/SimonSchick/followers","following_url":"https://api.github.com/users/SimonSchick/following{/other_user}","gists_url":"https://api.github.com/users/SimonSchick/gists{/gist_id}","starred_url":"https://api.github.com/users/SimonSchick/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SimonSchick/subscriptions","organizations_url":"https://api.github.com/users/SimonSchick/orgs","repos_url":"https://api.github.com/users/SimonSchick/repos","events_url":"https://api.github.com/users/SimonSchick/events{/privacy}","received_events_url":"https://api.github.com/users/SimonSchick/received_events","type":"User","site_admin":false},"body":"redundant else","created_at":"2018-04-27T15:48:50Z","updated_at":"2018-04-27T15:51:44Z","html_url":"https://github.com/mixer/etcd3/pull/66#discussion_r184728963","pull_request_url":"https://api.github.com/repos/mixer/etcd3/pulls/66","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/mixer/etcd3/pulls/comments/184728963"},"html":{"href":"https://github.com/mixer/etcd3/pull/66#discussion_r184728963"},"pull_request":{"href":"https://api.github.com/repos/mixer/etcd3/pulls/66"}}},"pull_request":{"url":"https://api.github.com/repos/mixer/etcd3/pulls/66","id":173911879,"html_url":"https://github.com/mixer/etcd3/pull/66","diff_url":"https://github.com/mixer/etcd3/pull/66.diff","patch_url":"https://github.com/mixer/etcd3/pull/66.patch","issue_url":"https://api.github.com/repos/mixer/etcd3/issues/66","number":66,"state":"open","locked":false,"title":"Feature/election","user":{"login":"yujuiting","id":5251157,"avatar_url":"https://avatars2.githubusercontent.com/u/5251157?v=4","gravatar_id":"","url":"https://api.github.com/users/yujuiting","html_url":"https://github.com/yujuiting","followers_url":"https://api.github.com/users/yujuiting/followers","following_url":"https://api.github.com/users/yujuiting/following{/other_user}","gists_url":"https://api.github.com/users/yujuiting/gists{/gist_id}","starred_url":"https://api.github.com/users/yujuiting/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yujuiting/subscriptions","organizations_url":"https://api.github.com/users/yujuiting/orgs","repos_url":"https://api.github.com/users/yujuiting/repos","events_url":"https://api.github.com/users/yujuiting/events{/privacy}","received_events_url":"https://api.github.com/users/yujuiting/received_events","type":"User","site_admin":false},"body":"[Implement leader election](https://github.com/coreos/etcd/blob/master/clientv3/concurrency/election.go)\r\n\r\n```ts\r\nconst client = new Etcd3();\r\nconst election = new Election(client, 'singleton');\r\n\r\nasync function task() {\r\n  const rid = `${Math.random()}`;\r\n  // process will hang here until elected\r\n  await election.campaign(rid);\r\n  // do task\r\n}\r\n```","created_at":"2018-03-09T03:42:31Z","updated_at":"2018-04-27T15:51:44Z","closed_at":null,"merged_at":null,"merge_commit_sha":"817e4c9687cc389bbd4e4155d44c2f9838e3eb2c","assignee":{"login":"connor4312","id":2230985,"avatar_url":"https://avatars0.githubusercontent.com/u/2230985?v=4","gravatar_id":"","url":"https://api.github.com/users/connor4312","html_url":"https://github.com/connor4312","followers_url":"https://api.github.com/users/connor4312/followers","following_url":"https://api.github.com/users/connor4312/following{/other_user}","gists_url":"https://api.github.com/users/connor4312/gists{/gist_id}","starred_url":"https://api.github.com/users/connor4312/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/connor4312/subscriptions","organizations_url":"https://api.github.com/users/connor4312/orgs","repos_url":"https://api.github.com/users/connor4312/repos","events_url":"https://api.github.com/users/connor4312/events{/privacy}","received_events_url":"https://api.github.com/users/connor4312/received_events","type":"User","site_admin":false},"assignees":[{"login":"connor4312","id":2230985,"avatar_url":"https://avatars0.githubusercontent.com/u/2230985?v=4","gravatar_id":"","url":"https://api.github.com/users/connor4312","html_url":"https://github.com/connor4312","followers_url":"https://api.github.com/users/connor4312/followers","following_url":"https://api.github.com/users/connor4312/following{/other_user}","gists_url":"https://api.github.com/users/connor4312/gists{/gist_id}","starred_url":"https://api.github.com/users/connor4312/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/connor4312/subscriptions","organizations_url":"https://api.github.com/users/connor4312/orgs","repos_url":"https://api.github.com/users/connor4312/repos","events_url":"https://api.github.com/users/connor4312/events{/privacy}","received_events_url":"https://api.github.com/users/connor4312/received_events","type":"User","site_admin":false}],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/mixer/etcd3/pulls/66/commits","review_comments_url":"https://api.github.com/repos/mixer/etcd3/pulls/66/comments","review_comment_url":"https://api.github.com/repos/mixer/etcd3/pulls/comments{/number}","comments_url":"https://api.github.com/repos/mixer/etcd3/issues/66/comments","statuses_url":"https://api.github.com/repos/mixer/etcd3/statuses/7c3563facf52d62c0b368aa0ef24503c0ac09dd0","head":{"label":"17media:feature/election","ref":"feature/election","sha":"7c3563facf52d62c0b368aa0ef24503c0ac09dd0","user":{"login":"17media","id":17403626,"avatar_url":"https://avatars0.githubusercontent.com/u/17403626?v=4","gravatar_id":"","url":"https://api.github.com/users/17media","html_url":"https://github.com/17media","followers_url":"https://api.github.com/users/17media/followers","following_url":"https://api.github.com/users/17media/following{/other_user}","gists_url":"https://api.github.com/users/17media/gists{/gist_id}","starred_url":"https://api.github.com/users/17media/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/17media/subscriptions","organizations_url":"https://api.github.com/users/17media/orgs","repos_url":"https://api.github.com/users/17media/repos","events_url":"https://api.github.com/users/17media/events{/privacy}","received_events_url":"https://api.github.com/users/17media/received_events","type":"Organization","site_admin":false},"repo":{"id":124372874,"name":"etcd3","full_name":"17media/etcd3","owner":{"login":"17media","id":17403626,"avatar_url":"https://avatars0.githubusercontent.com/u/17403626?v=4","gravatar_id":"","url":"https://api.github.com/users/17media","html_url":"https://github.com/17media","followers_url":"https://api.github.com/users/17media/followers","following_url":"https://api.github.com/users/17media/following{/other_user}","gists_url":"https://api.github.com/users/17media/gists{/gist_id}","starred_url":"https://api.github.com/users/17media/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/17media/subscriptions","organizations_url":"https://api.github.com/users/17media/orgs","repos_url":"https://api.github.com/users/17media/repos","events_url":"https://api.github.com/users/17media/events{/privacy}","received_events_url":"https://api.github.com/users/17media/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/17media/etcd3","description":":bookmark: Node.js client for etcd3","fork":true,"url":"https://api.github.com/repos/17media/etcd3","forks_url":"https://api.github.com/repos/17media/etcd3/forks","keys_url":"https://api.github.com/repos/17media/etcd3/keys{/key_id}","collaborators_url":"https://api.github.com/repos/17media/etcd3/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/17media/etcd3/teams","hooks_url":"https://api.github.com/repos/17media/etcd3/hooks","issue_events_url":"https://api.github.com/repos/17media/etcd3/issues/events{/number}","events_url":"https://api.github.com/repos/17media/etcd3/events","assignees_url":"https://api.github.com/repos/17media/etcd3/assignees{/user}","branches_url":"https://api.github.com/repos/17media/etcd3/branches{/branch}","tags_url":"https://api.github.com/repos/17media/etcd3/tags","blobs_url":"https://api.github.com/repos/17media/etcd3/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/17media/etcd3/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/17media/etcd3/git/refs{/sha}","trees_url":"https://api.github.com/repos/17media/etcd3/git/trees{/sha}","statuses_url":"https://api.github.com/repos/17media/etcd3/statuses/{sha}","languages_url":"https://api.github.com/repos/17media/etcd3/languages","stargazers_url":"https://api.github.com/repos/17media/etcd3/stargazers","contributors_url":"https://api.github.com/repos/17media/etcd3/contributors","subscribers_url":"https://api.github.com/repos/17media/etcd3/subscribers","subscription_url":"https://api.github.com/repos/17media/etcd3/subscription","commits_url":"https://api.github.com/repos/17media/etcd3/commits{/sha}","git_commits_url":"https://api.github.com/repos/17media/etcd3/git/commits{/sha}","comments_url":"https://api.github.com/repos/17media/etcd3/comments{/number}","issue_comment_url":"https://api.github.com/repos/17media/etcd3/issues/comments{/number}","contents_url":"https://api.github.com/repos/17media/etcd3/contents/{+path}","compare_url":"https://api.github.com/repos/17media/etcd3/compare/{base}...{head}","merges_url":"https://api.github.com/repos/17media/etcd3/merges","archive_url":"https://api.github.com/repos/17media/etcd3/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/17media/etcd3/downloads","issues_url":"https://api.github.com/repos/17media/etcd3/issues{/number}","pulls_url":"https://api.github.com/repos/17media/etcd3/pulls{/number}","milestones_url":"https://api.github.com/repos/17media/etcd3/milestones{/number}","notifications_url":"https://api.github.com/repos/17media/etcd3/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/17media/etcd3/labels{/name}","releases_url":"https://api.github.com/repos/17media/etcd3/releases{/id}","deployments_url":"https://api.github.com/repos/17media/etcd3/deployments","created_at":"2018-03-08T10:04:00Z","updated_at":"2018-03-08T10:04:02Z","pushed_at":"2018-04-27T02:40:09Z","git_url":"git://github.com/17media/etcd3.git","ssh_url":"git@github.com:17media/etcd3.git","clone_url":"https://github.com/17media/etcd3.git","svn_url":"https://github.com/17media/etcd3","homepage":"https://mixer.github.io/etcd3/classes/index_.etcd3.html","size":955,"stargazers_count":0,"watchers_count":0,"language":"TypeScript","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":1,"license":{"key":"other","name":"Other","spdx_id":null,"url":null},"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"mixer:master","ref":"master","sha":"f3282649f1ee665492364a6296bdf3fac8498ac7","user":{"login":"mixer","id":11798804,"avatar_url":"https://avatars3.githubusercontent.com/u/11798804?v=4","gravatar_id":"","url":"https://api.github.com/users/mixer","html_url":"https://github.com/mixer","followers_url":"https://api.github.com/users/mixer/followers","following_url":"https://api.github.com/users/mixer/following{/other_user}","gists_url":"https://api.github.com/users/mixer/gists{/gist_id}","starred_url":"https://api.github.com/users/mixer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mixer/subscriptions","organizations_url":"https://api.github.com/users/mixer/orgs","repos_url":"https://api.github.com/users/mixer/repos","events_url":"https://api.github.com/users/mixer/events{/privacy}","received_events_url":"https://api.github.com/users/mixer/received_events","type":"Organization","site_admin":false},"repo":{"id":87667333,"name":"etcd3","full_name":"mixer/etcd3","owner":{"login":"mixer","id":11798804,"avatar_url":"https://avatars3.githubusercontent.com/u/11798804?v=4","gravatar_id":"","url":"https://api.github.com/users/mixer","html_url":"https://github.com/mixer","followers_url":"https://api.github.com/users/mixer/followers","following_url":"https://api.github.com/users/mixer/following{/other_user}","gists_url":"https://api.github.com/users/mixer/gists{/gist_id}","starred_url":"https://api.github.com/users/mixer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mixer/subscriptions","organizations_url":"https://api.github.com/users/mixer/orgs","repos_url":"https://api.github.com/users/mixer/repos","events_url":"https://api.github.com/users/mixer/events{/privacy}","received_events_url":"https://api.github.com/users/mixer/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/mixer/etcd3","description":":bookmark: Node.js client for etcd3","fork":false,"url":"https://api.github.com/repos/mixer/etcd3","forks_url":"https://api.github.com/repos/mixer/etcd3/forks","keys_url":"https://api.github.com/repos/mixer/etcd3/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mixer/etcd3/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mixer/etcd3/teams","hooks_url":"https://api.github.com/repos/mixer/etcd3/hooks","issue_events_url":"https://api.github.com/repos/mixer/etcd3/issues/events{/number}","events_url":"https://api.github.com/repos/mixer/etcd3/events","assignees_url":"https://api.github.com/repos/mixer/etcd3/assignees{/user}","branches_url":"https://api.github.com/repos/mixer/etcd3/branches{/branch}","tags_url":"https://api.github.com/repos/mixer/etcd3/tags","blobs_url":"https://api.github.com/repos/mixer/etcd3/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mixer/etcd3/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mixer/etcd3/git/refs{/sha}","trees_url":"https://api.github.com/repos/mixer/etcd3/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mixer/etcd3/statuses/{sha}","languages_url":"https://api.github.com/repos/mixer/etcd3/languages","stargazers_url":"https://api.github.com/repos/mixer/etcd3/stargazers","contributors_url":"https://api.github.com/repos/mixer/etcd3/contributors","subscribers_url":"https://api.github.com/repos/mixer/etcd3/subscribers","subscription_url":"https://api.github.com/repos/mixer/etcd3/subscription","commits_url":"https://api.github.com/repos/mixer/etcd3/commits{/sha}","git_commits_url":"https://api.github.com/repos/mixer/etcd3/git/commits{/sha}","comments_url":"https://api.github.com/repos/mixer/etcd3/comments{/number}","issue_comment_url":"https://api.github.com/repos/mixer/etcd3/issues/comments{/number}","contents_url":"https://api.github.com/repos/mixer/etcd3/contents/{+path}","compare_url":"https://api.github.com/repos/mixer/etcd3/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mixer/etcd3/merges","archive_url":"https://api.github.com/repos/mixer/etcd3/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mixer/etcd3/downloads","issues_url":"https://api.github.com/repos/mixer/etcd3/issues{/number}","pulls_url":"https://api.github.com/repos/mixer/etcd3/pulls{/number}","milestones_url":"https://api.github.com/repos/mixer/etcd3/milestones{/number}","notifications_url":"https://api.github.com/repos/mixer/etcd3/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mixer/etcd3/labels{/name}","releases_url":"https://api.github.com/repos/mixer/etcd3/releases{/id}","deployments_url":"https://api.github.com/repos/mixer/etcd3/deployments","created_at":"2017-04-08T22:11:31Z","updated_at":"2018-04-27T08:41:58Z","pushed_at":"2018-04-27T02:40:09Z","git_url":"git://github.com/mixer/etcd3.git","ssh_url":"git@github.com:mixer/etcd3.git","clone_url":"https://github.com/mixer/etcd3.git","svn_url":"https://github.com/mixer/etcd3","homepage":"https://mixer.github.io/etcd3/classes/index_.etcd3.html","size":856,"stargazers_count":67,"watchers_count":67,"language":"TypeScript","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":16,"mirror_url":null,"archived":false,"open_issues_count":11,"license":{"key":"other","name":"Other","spdx_id":null,"url":null},"forks":16,"open_issues":11,"watchers":67,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/mixer/etcd3/pulls/66"},"html":{"href":"https://github.com/mixer/etcd3/pull/66"},"issue":{"href":"https://api.github.com/repos/mixer/etcd3/issues/66"},"comments":{"href":"https://api.github.com/repos/mixer/etcd3/issues/66/comments"},"review_comments":{"href":"https://api.github.com/repos/mixer/etcd3/pulls/66/comments"},"review_comment":{"href":"https://api.github.com/repos/mixer/etcd3/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/mixer/etcd3/pulls/66/commits"},"statuses":{"href":"https://api.github.com/repos/mixer/etcd3/statuses/7c3563facf52d62c0b368aa0ef24503c0ac09dd0"}},"author_association":"NONE"}}
{"actor":{"display_login":"SimonSchick"}}