{"action":"created","comment":{"url":"https://api.github.com/repos/nodejs/node-eps/pulls/comments/76310374","id":76310374,"diff_hunk":"@@ -0,0 +1,540 @@\n+| Title  | AsyncWrap API |\n+|--------|---------------|\n+| Author | @trevnorris   |\n+| Status | DRAFT         |\n+| Date   | 2016-04-05    |\n+\n+## Description\n+\n+Since its initial introduction along side the `AsyncListener` API, `AsyncWrap`\n+has slowly evolved to ensure a generalized API that would serve as a solid base\n+for module authors who wished to add listeners to the event loop's life cycle.\n+Some of the use cases `AsyncWrap` has covered are long stack traces,\n+continuation local storage, profiling of asynchronous requests and resource\n+tracking.\n+\n+It must be clarified that the `AsyncWrap` API is not meant to abstract away\n+Node's implementation details, and is in fact intentional. Observing how\n+operations are performed, not simply how they appear to perform from the public\n+API, is a key point in its usability. For a real world example, a user was\n+having difficulty figuring out why their `http.get()` calls were taking so\n+long. Because `AsyncHooks` exposed the `dns.lookup()` request to resolve the\n+host being made by the `net` module it was trivial to write a performance\n+measurement that exposed the hidden culprit. Which was that DNS resolution was\n+taking much longer than expected.\n+\n+A small amount of abstraction is necessary to accommodate the conceptual nature\n+of the API. For example, the `HTTPParser` is not destructed but instead placed\n+into an unused pool to be used later. Even so, at the time it is placed into\n+the pool  the `destroy()` callback will run and then the id assigned to that\n+instance will be removed. If that resource is again requested then it will be\n+assigned a new id and will run `init()` again.\n+\n+\n+## Goals\n+\n+The intent for the initial release is to provide the most minimal set of API\n+hooks that don't inhibit module authors from writing tools addressing anything\n+in this problem space. In order to remain minimal, all potential features for\n+initial release will first be judged on whether they can be achieved by the\n+existing public API. If so then such a feature won't be included.\n+\n+If the feature cannot be done with the existing public API then discussion will\n+be had on whether requested functionality should be included in the initial\n+API. If so then the best course of action to include the capabilities of the\n+request will be discussed and performed. Meaning, the resulting API may not be\n+the same as initially requested, but the user will be able to achieve the same\n+end result.\n+\n+Performance impact of `AsyncWrap` should be zero if not being used, and near\n+zero while being used. The performance overhead of `AsyncHook` callbacks\n+supplied by the user should account for essentially all the performance\n+overhead.\n+\n+\n+## Terminology\n+\n+Because of the potential ambiguity for those not familiar with the terms\n+\"handle\" and \"request\" they should be well defined.\n+\n+Handles are a reference to a system resource. The resource handle can be a\n+simple identifier, such as a file descriptor, or it can be a pointer that\n+allows access to further information, such as `uv_tcp_t`. Realize that the\n+latter still relies on a file descriptor, but has been encapsulated in a data\n+structure containing additional information that allows the application to work\n+with it.\n+\n+Requests are short lived data structures created to accomplish one task. The\n+callback for a request should always and only ever fire one time. When the\n+assigned task has either completed or encountered an error. Requests are used\n+by handles to perform work. Such as accepting a new connection or writing data\n+to disk.\n+\n+\n+## API\n+\n+### Overview\n+\n+```js\n+// Standard way of requiring a module. Snake case follows core module\n+// convention.\n+const async_wrap = require('async_wrap');\n+\n+// Return the id of the current execution context. Useful for tracking state\n+// and retrieving the handle of the current parent without needing to use an\n+// AsyncHook().\n+const id = async_wrap.currentId();\n+\n+// Generate new uid for user API.\n+const id = async_wrap.newUid();\n+\n+// Create a new instance and add each callback to the corresponding hook.\n+var asyncHook = new async_wrap.AsyncHook({ init, before, after, destroy });\n+\n+// Allow callbacks of this AsyncHook instance to fire. This is not an implicit\n+// action after running the constructor, and must be explicitly run to begin\n+// executing callbacks.\n+asyncHook.enable();\n+\n+// Disable listening for new asynchronous events. Though this will not prevent\n+// callbacks from firing on asynchronous chains that have already run within\n+// the scope of an enabled AsyncHook instance.\n+asyncHook.disable();\n+\n+// Enable hooks for the current synchronous execution scope. This will ensure\n+// the hooks are not in effect in case of multiple returns, or if an exception\n+// is thrown.\n+asyncHook.syncScope();\n+\n+// Unlike disable(), prevent any hook callback from firing again in the future.\n+// Future hooks can be added by running syncScope()/enable() again.\n+// TODO: Investigation is needed to know whether this is possible.\n+asyncHook.remove();\n+\n+// init() is called during object construction. The handle may not have\n+// completed construction when this callback runs. So all fields of the\n+// handle referenced by \"id\" may not have been populated.\n+function init(id, type, parentId, handle) { }\n+\n+// before() is called just before the handle's callback is called. It can be\n+// called 0-N times for handles (e.g. TCPWrap), and should be called exactly 1\n+// time for requests (e.g. FSReqWrap).\n+function before(id) { }\n+\n+// after() is called just after the handle's callback has finished, and will be\n+// called regardless whether the handle's callback threw.\n+function after(id) { }\n+\n+// destroy() is called when an AsyncWrap instance is destroyed. In cases like\n+// HTTPParser where the resource is reused, or timers where the handle is only\n+// a JS object, destroy() will be triggered manually soon after after() has\n+// completed.\n+function destroy(id) { }\n+```\n+\n+\n+### `async_wrap`\n+\n+The object returned from `require('async_wrap')`.\n+\n+\n+#### `async_wrap.currentId()`\n+\n+Return the id of the current execution context. Useful to track the\n+asynchronous execution chain. It can also be used to propagate state without\n+needing to use the `AsyncHook` constructor. For example:\n+\n+```js\n+const map = new Map();\n+\n+net.createServer((c) => {\n+  // TODO: In order for this to be useful it needs to be possible to get the\n+  // id of a given handle. Otherwise it'll be impossible to store state at\n+  // moments like this for later usage using the handle's id.\n+}).listen(8111);\n+```\n+\n+\n+### Constructor: `AsyncHook(callbacks)`\n+\n+The `AsyncHook` constructor returns an instance that contains information about\n+the callbacks that are to fire during specific asynchronous events in the\n+lifetime of the event loop. The focal point of these calls centers around the\n+lifetime of `AsyncWrap`. These callbacks will also be called to emulate the\n+lifetime of handles and requests that do not fit this model. For example,\n+`HTTPParser` instances are recycled to improve performance. So the `destroy()`\n+callback would be called manually after a connection is done using it. Just\n+before it's placed back into the unused resource pool.\n+\n+All callbacks are optional. So, for example, if only resource cleanup needs to\n+be tracked then only the `destroy()` callback needs to be passed.\n+\n+**Error Handling**: If any callback throws the application will print the stack\n+trace and exit. The exit path does follow that of any uncaught exception,\n+except for the fact that it is not catchable by an uncaught exception handler,\n+so any `'exit'` callbacks will fire. Unless the application is run with\n+`--abort-on-uncaught-exception`. In which case a stack trace will be printed\n+and the application will exit, leaving a core file.\n+\n+The reason for this error handling behavior is that these callbacks are running\n+at potentially volatile points in an object's lifetime. For example during\n+class construction and destruction. Because of this, it is deemed necessary to\n+bring down the process quickly as to prevent an unintentional abort in the\n+future. This is subject to change in the future if a comprehensive analysis is\n+performed to ensure an exception can follow the normal control flow without\n+unintentional side effects.\n+\n+\n+#### `asyncHook.enable()`\n+\n+Enable the callbacks for a given `AsyncHook` instance. Once a callback fires on\n+an asynchronous event they will continue to fire for all nested asynchronous\n+events. Even after the instance has been disabled. The following is an example\n+of how this operates:\n+\n+```js\n+const async_wrap = require('async_wrap');\n+const hooks = new async_wrap.AsyncHook({ init });\n+hooks.enable();\n+\n+net.createServer((c) => {\n+  // Even though the hooks have been disabled by the time this callback runs\n+  // the callbacks attached to \"hooks\" will still fire.\n+});\n+\n+hooks.disable();\n+```\n+\n+Callbacks are not implicitly enabled after an instance is created. The reason\n+for this is to not make any assumptions about the user's use case. Since\n+constructing the `asyncHook` during startup, but not using it until later is, a\n+reasonable use case. This API is meant to err on the side of requiring explicit\n+instructions from the user.\n+\n+\n+#### `asyncHook.disable()`\n+\n+Disable the callbacks for a given `AsyncHook` instance. Doing this will prevent\n+the `init()`, etc., calls from firing for any new roots of asynchronous call\n+stacks, but will not prevent existing asynchronous call stacks that have\n+already been captured by the `AsyncHook` instance from continuing to fire.\n+\n+Be careful to always disable the hooks when they are no longer needed.\n+Especially in cases where there are multiple return statements. If the hooks\n+are not disabled then they will stay active indefinitely. The following is an\n+example of how a call to `disable()` may accidentally left out:\n+\n+```js\n+const hooks = new async_wrap.AsyncHook(/* ... */);\n+\n+crypto.randomBytes(1024, (err, data) => {\n+  hooks.enable();\n+  try {\n+    fs.writeFileSync(path, data);\n+  } catch (e) {\n+    console.error('file write failed');\n+    // Notice the early return and failure to run hooks.disable(), which will\n+    // allow the hooks callbacks to continue firing indefinitely.\n+    return;\n+  }\n+  hooks.disable();\n+});\n+```\n+\n+While not included as part of initial development, it should be possible in the\n+future to allow selective tracking of asynchronous call stacks. The following\n+example demonstrates this:\n+\n+```js\n+const async_wrap = require('async_wrap');\n+const net = require('net');\n+\n+// Pretend init, before, after are all defined\n+const asyncHook = async_wrap.createHook({ init, before, after });\n+asyncHook.enable();\n+\n+net.createServer((c) => {\n+  // Only want to follow connections that match IP range.\n+  if (ipRangeRegExp.test(c.address().address))\n+    asyncHook.disable();\n+}).listen(PORT);\n+```\n+\n+At which point no further calls to the hooks on that instance will be made for\n+that asynchronous branch.\n+\n+\n+#### `asyncHook.syncScope()`\n+\n+Enable capture of asynchronous events until the current synchronous code\n+execution has completed. This is basically a small amount of sugar to prevent\n+accidentally forgetting to `disable()` a set of hooks in a function with\n+multiple returns. Also in case an error is thrown and caught by a domain or\n+`uncaughtException`.\n+\n+Say an application only wishes to observe the asynchronous calls made from\n+another asynchronous callback. Without `syncScope()` the call would look like\n+so:\n+\n+```js\n+fs.readFile(path, (err, data) => {\n+  asyncHook.enable();\n+  var e = asyncDataProcessing(data);\n+  if (e) {\n+    asyncHook.disable();\n+    return;\n+  }\n+  doMoreAsyncStuff();\n+  asyncHook.disable();\n+});\n+```\n+\n+But using `syncScope()` all of that is handled for you:\n+\n+```js\n+fs.readFile(path, (err, data) => {\n+  asyncHook.syncScope();\n+  var e = asyncDataProcessing(data);\n+  if (e)\n+    return;\n+  doMoreAsyncStuff();\n+});\n+```\n+\n+**Note**: Notice that this specifically states the synchronous scope. Not the\n+function scope. So if this is used several call stacks deep keep in mind that\n+the hooks will be enabled until the stack completely unwinds.\n+\n+\n+#### `asyncHook.remove()`\n+\n+Unlike `disable()`, `remove()` prevents the callbacks from firing again.\n+Regardless of whether they have been attached to an asynchronous execution\n+chain or not.\n+\n+```js\n+net.createServer((c) => {\n+  asyncHook.syncScope();\n+  c.on('data', (chunk) => {});\n+  c.on('end', () => {});\n+});\n+\n+setTimeout(() => {\n+  // All new connections that have attached asyncHooks created in the last five\n+  // seconds will not fire again.\n+  asyncHook.remove();\n+}, 5000);\n+```\n+\n+\n+### Hook Callbacks\n+\n+Key events in the lifetime of asynchronous events have been categorized into\n+four areas. On instantiation, before/after the callback is called and when the\n+instance is destructed. For cases where resources are reused, instantiation and\n+destructor calls are emulated.\n+\n+\n+#### `init(id, type, parentId, handle)`\n+\n+* `id` {Number}\n+* `type` {String}\n+* `parentId` {Number}\n+* `handle` {Object}\n+\n+Called when a class is constructed that has the possibility to trigger an\n+asynchronous event. This does not mean the instance will trigger a\n+`before()`/`after()` event before `destroy()` is called. Only that the\n+possibility exists.\n+\n+This behavior can be observed by doing something like opening a resource then\n+closing it before the resource can be used. The following snippet demonstrates\n+this.\n+\n+```js\n+require('net').createServer().listen(8080, function() { this.close() });\n+```\n+\n+Every instance is assigned a unique id. Taking advantage of the fraction space\n+in the 64-bit IEEE 754 that ECMAScript defines as the **Number** type, the\n+number of id's that can be assigned are `2^53 - 1` (also defined as\n+`Number.MAX_SAFE_INTEGER`). At this size node can assign a new id every 100\n+nanoseconds and not run out for over 28 years. Because of this circumstance it\n+is not deemed necessary to use an alternative approach that would account for\n+the id to wrap around.\n+\n+The `type` is a String that represents the type of handle that caused `init()`\n+to fire. Generally it will be the name of the handle's constructor. Some\n+examples include `TCP`, `GetAddrInfo` and `HTTPParser`. Users will be able to\n+define their own `type` when using the public API.\n+\n+`parentId` is the unique id of either the async resource at the top of the JS\n+stack when `init()` was called, or the originator of the new resource. For\n+example, when a connection is made to a TCP server there is no JS stack\n+available when the connection's constructor is executed. Meaning there would be\n+no `parentId` available on the JS stack. Instead the TCP server's unique id is\n+manually passed to the client's constructor and propagated to the `init()`'s\n+`parentId`.\n+\n+The constructed `handle` is passed to `init()`. The structure of any object\n+passed to `init()` has no guarantee of stability, even through patch updates.\n+It's meant to be used for debugging the application when additional, and\n+specific, information is needed. Make sure not to hold a reference to this\n+indefinitely or else the GC won't be able to collect it after node has removed\n+its own reference to the handle.\n+\n+\n+#### `before(id)`\n+\n+* `id` {Number}\n+\n+Called just before the return callback is called after completing an\n+asynchronous request. Or called on handles with events such as receiving a new\n+connection. For requests, such as `fs.open()`, this should be called exactly\n+once. For handles, such as a TCP server, this may be called 0-N times.\n+\n+\n+#### `after(id)`\n+\n+* `id` {Number}\n+\n+Called immediately after the return callback is completed. If a callback throws\n+but is not caught then the process will exit immediately without calling\n+`after()`.\n+\n+\n+#### `destroy(id)`\n+\n+* `id` {Number}\n+\n+Called either when the class destructor is run, or if the resource is marked as\n+free. The destructor will usually run when explicitly called (the case for\n+handles) or when a request has completed. In several cases this will be\n+triggered by GC. In the case of shared or cached resources, such as\n+`HTTPParser`, `destroy()` will be called manually when the TCP connection is no\n+longer in need of it. Every subsequent use of a shared resource will have a new\n+unique id.\n+\n+\n+## Embedder API\n+\n+Library developers that handle their own I/O will need to hook into the\n+`AsyncWrap` API so that all the appropriate callbacks are called. To\n+accommodate this both a C++ and JS API is provided.\n+\n+\n+### Native API\n+\n+```cpp\n+// Helper class users can inherit from, but is not necessary. It will\n+// automatically call all four callbacks.\n+class AsyncHook {\n+  public:\n+    AsyncHook(Local<Object> handle, const char* name);\n+    ~AsyncHook();\n+    MaybeLocal<Value> MakeCallback(\n+        const Local<Function> callback,\n+        int argc,\n+        Local<Value>* argv);\n+    Local<Object> handle();\n+    uint64_t get_uid();\n+  private:\n+    AsyncHook();\n+    Persistent<Object> handle_;\n+    const char* name_;\n+    uint64_t uid_;\n+}\n+\n+// Returns the id of the current execution context. If the return value is\n+// zero then no execution has been set. This will happen if the user handles\n+// I/O from native code.\n+uint64_t node::GetCurrentId();\n+\n+// If the native API doesn't inherit from the helper class then the callbacks\n+// must be triggered manually. This triggers the init() callback. The return\n+// value is the uid assigned to the handle.\n+// TODO(trevnorris): This always needs to be called so that the handle can be\n+// placed on the Map for future query.\n+uint64_t node::EmitAsyncHandleInit(Local<Object> handle);\n+\n+// Emit the destroy() callback.\n+void node::EmitAsyncHandleDestroy(uint64_t id);\n+\n+// An API specific to emit before/after callbacks is unnecessary because\n+// MakeCallback will automatically call them for you.\n+MaybeLocal<Value> node::MakeCallback(Isolate* isolate,\n+                                     Local<Object> recv,\n+                                     Local<Function> callback,\n+                                     uint64_t id,\n+                                     int argc,\n+                                     Local<Value>* argv);\n+```\n+\n+\n+### JS API","path":"XXX-asyncwrap-api.md","position":474,"original_position":474,"commit_id":"217353e033b816360b77a43ce6f4eb62010f7e49","original_commit_id":"cff2c66330d8fef0f012aac97ef02f4a4bee0934","user":{"login":"AndreasMadsen","id":505333,"avatar_url":"https://avatars.githubusercontent.com/u/505333?v=3","gravatar_id":"","url":"https://api.github.com/users/AndreasMadsen","html_url":"https://github.com/AndreasMadsen","followers_url":"https://api.github.com/users/AndreasMadsen/followers","following_url":"https://api.github.com/users/AndreasMadsen/following{/other_user}","gists_url":"https://api.github.com/users/AndreasMadsen/gists{/gist_id}","starred_url":"https://api.github.com/users/AndreasMadsen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AndreasMadsen/subscriptions","organizations_url":"https://api.github.com/users/AndreasMadsen/orgs","repos_url":"https://api.github.com/users/AndreasMadsen/repos","events_url":"https://api.github.com/users/AndreasMadsen/events{/privacy}","received_events_url":"https://api.github.com/users/AndreasMadsen/received_events","type":"User","site_admin":false},"body":"Do we assume that the user doesn't call `after` in another tick (as in nextTick)?\r\n\r\n```javascript\r\nconst id = async_wrap.emitInit({});\r\nsetTimeout(function () { // any async thing \r\n  async_wrap.emitBefore(id);\r\n  process.nextTick(function () {\r\n    async_wrap.emitAfter(id); // calling after & destroy in another tick\r\n    async_wrap.emitDestroy(id);\r\n  });\r\n});\r\n```","created_at":"2016-08-25T19:36:49Z","updated_at":"2016-08-25T19:36:50Z","html_url":"https://github.com/nodejs/node-eps/pull/18#discussion_r76310374","pull_request_url":"https://api.github.com/repos/nodejs/node-eps/pulls/18","_links":{"self":{"href":"https://api.github.com/repos/nodejs/node-eps/pulls/comments/76310374"},"html":{"href":"https://github.com/nodejs/node-eps/pull/18#discussion_r76310374"},"pull_request":{"href":"https://api.github.com/repos/nodejs/node-eps/pulls/18"}}},"pull_request":{"url":"https://api.github.com/repos/nodejs/node-eps/pulls/18","id":68137447,"html_url":"https://github.com/nodejs/node-eps/pull/18","diff_url":"https://github.com/nodejs/node-eps/pull/18.diff","patch_url":"https://github.com/nodejs/node-eps/pull/18.patch","issue_url":"https://api.github.com/repos/nodejs/node-eps/issues/18","number":18,"state":"open","locked":false,"title":"AsyncWrap public API proposal","user":{"login":"trevnorris","id":289174,"avatar_url":"https://avatars.githubusercontent.com/u/289174?v=3","gravatar_id":"","url":"https://api.github.com/users/trevnorris","html_url":"https://github.com/trevnorris","followers_url":"https://api.github.com/users/trevnorris/followers","following_url":"https://api.github.com/users/trevnorris/following{/other_user}","gists_url":"https://api.github.com/users/trevnorris/gists{/gist_id}","starred_url":"https://api.github.com/users/trevnorris/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/trevnorris/subscriptions","organizations_url":"https://api.github.com/users/trevnorris/orgs","repos_url":"https://api.github.com/users/trevnorris/repos","events_url":"https://api.github.com/users/trevnorris/events{/privacy}","received_events_url":"https://api.github.com/users/trevnorris/received_events","type":"User","site_admin":false},"body":"After much investigation and communication this is the API that has\r\nsurfaced. Meant to be minimal, not impose any performance penalty to\r\ncore when not being used, and minimal impact when it is used, this\r\nshould serve public needs that have been expressed over the last two\r\nyears.\r\n\r\n@nodejs/ctc I'd like the initial review explicitly from the CTC before this is opened for too much external debate. Because experience has shown me that there will be suggestions/changes for those who want specific features and/or additions to suit their specific use case. Usually not taking the time to realize that this API is enough. They just need to write the additional code for the hooks.","created_at":"2016-04-27T22:43:35Z","updated_at":"2016-08-25T19:36:50Z","closed_at":null,"merged_at":null,"merge_commit_sha":"7843b6ed4dd844f8c670d033ad492b6156cf6fa9","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/nodejs/node-eps/pulls/18/commits","review_comments_url":"https://api.github.com/repos/nodejs/node-eps/pulls/18/comments","review_comment_url":"https://api.github.com/repos/nodejs/node-eps/pulls/comments{/number}","comments_url":"https://api.github.com/repos/nodejs/node-eps/issues/18/comments","statuses_url":"https://api.github.com/repos/nodejs/node-eps/statuses/217353e033b816360b77a43ce6f4eb62010f7e49","head":{"label":"nodejs:async-wrap-ep","ref":"async-wrap-ep","sha":"217353e033b816360b77a43ce6f4eb62010f7e49","user":{"login":"nodejs","id":9950313,"avatar_url":"https://avatars.githubusercontent.com/u/9950313?v=3","gravatar_id":"","url":"https://api.github.com/users/nodejs","html_url":"https://github.com/nodejs","followers_url":"https://api.github.com/users/nodejs/followers","following_url":"https://api.github.com/users/nodejs/following{/other_user}","gists_url":"https://api.github.com/users/nodejs/gists{/gist_id}","starred_url":"https://api.github.com/users/nodejs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nodejs/subscriptions","organizations_url":"https://api.github.com/users/nodejs/orgs","repos_url":"https://api.github.com/users/nodejs/repos","events_url":"https://api.github.com/users/nodejs/events{/privacy}","received_events_url":"https://api.github.com/users/nodejs/received_events","type":"Organization","site_admin":false},"repo":{"id":48204104,"name":"node-eps","full_name":"nodejs/node-eps","owner":{"login":"nodejs","id":9950313,"avatar_url":"https://avatars.githubusercontent.com/u/9950313?v=3","gravatar_id":"","url":"https://api.github.com/users/nodejs","html_url":"https://github.com/nodejs","followers_url":"https://api.github.com/users/nodejs/followers","following_url":"https://api.github.com/users/nodejs/following{/other_user}","gists_url":"https://api.github.com/users/nodejs/gists{/gist_id}","starred_url":"https://api.github.com/users/nodejs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nodejs/subscriptions","organizations_url":"https://api.github.com/users/nodejs/orgs","repos_url":"https://api.github.com/users/nodejs/repos","events_url":"https://api.github.com/users/nodejs/events{/privacy}","received_events_url":"https://api.github.com/users/nodejs/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/nodejs/node-eps","description":"Node.js Enhancement Proposals for discussion on future API additions/changes to Node core","fork":false,"url":"https://api.github.com/repos/nodejs/node-eps","forks_url":"https://api.github.com/repos/nodejs/node-eps/forks","keys_url":"https://api.github.com/repos/nodejs/node-eps/keys{/key_id}","collaborators_url":"https://api.github.com/repos/nodejs/node-eps/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/nodejs/node-eps/teams","hooks_url":"https://api.github.com/repos/nodejs/node-eps/hooks","issue_events_url":"https://api.github.com/repos/nodejs/node-eps/issues/events{/number}","events_url":"https://api.github.com/repos/nodejs/node-eps/events","assignees_url":"https://api.github.com/repos/nodejs/node-eps/assignees{/user}","branches_url":"https://api.github.com/repos/nodejs/node-eps/branches{/branch}","tags_url":"https://api.github.com/repos/nodejs/node-eps/tags","blobs_url":"https://api.github.com/repos/nodejs/node-eps/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/nodejs/node-eps/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/nodejs/node-eps/git/refs{/sha}","trees_url":"https://api.github.com/repos/nodejs/node-eps/git/trees{/sha}","statuses_url":"https://api.github.com/repos/nodejs/node-eps/statuses/{sha}","languages_url":"https://api.github.com/repos/nodejs/node-eps/languages","stargazers_url":"https://api.github.com/repos/nodejs/node-eps/stargazers","contributors_url":"https://api.github.com/repos/nodejs/node-eps/contributors","subscribers_url":"https://api.github.com/repos/nodejs/node-eps/subscribers","subscription_url":"https://api.github.com/repos/nodejs/node-eps/subscription","commits_url":"https://api.github.com/repos/nodejs/node-eps/commits{/sha}","git_commits_url":"https://api.github.com/repos/nodejs/node-eps/git/commits{/sha}","comments_url":"https://api.github.com/repos/nodejs/node-eps/comments{/number}","issue_comment_url":"https://api.github.com/repos/nodejs/node-eps/issues/comments{/number}","contents_url":"https://api.github.com/repos/nodejs/node-eps/contents/{+path}","compare_url":"https://api.github.com/repos/nodejs/node-eps/compare/{base}...{head}","merges_url":"https://api.github.com/repos/nodejs/node-eps/merges","archive_url":"https://api.github.com/repos/nodejs/node-eps/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/nodejs/node-eps/downloads","issues_url":"https://api.github.com/repos/nodejs/node-eps/issues{/number}","pulls_url":"https://api.github.com/repos/nodejs/node-eps/pulls{/number}","milestones_url":"https://api.github.com/repos/nodejs/node-eps/milestones{/number}","notifications_url":"https://api.github.com/repos/nodejs/node-eps/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/nodejs/node-eps/labels{/name}","releases_url":"https://api.github.com/repos/nodejs/node-eps/releases{/id}","deployments_url":"https://api.github.com/repos/nodejs/node-eps/deployments","created_at":"2015-12-17T23:39:07Z","updated_at":"2016-08-23T08:59:58Z","pushed_at":"2016-08-25T18:51:21Z","git_url":"git://github.com/nodejs/node-eps.git","ssh_url":"git@github.com:nodejs/node-eps.git","clone_url":"https://github.com/nodejs/node-eps.git","svn_url":"https://github.com/nodejs/node-eps","homepage":null,"size":148,"stargazers_count":159,"watchers_count":159,"language":null,"has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":22,"mirror_url":null,"open_issues_count":18,"forks":22,"open_issues":18,"watchers":159,"default_branch":"master"}},"base":{"label":"nodejs:master","ref":"master","sha":"f16f7707ba2814bd545e4fced9c5a7d5a5d828de","user":{"login":"nodejs","id":9950313,"avatar_url":"https://avatars.githubusercontent.com/u/9950313?v=3","gravatar_id":"","url":"https://api.github.com/users/nodejs","html_url":"https://github.com/nodejs","followers_url":"https://api.github.com/users/nodejs/followers","following_url":"https://api.github.com/users/nodejs/following{/other_user}","gists_url":"https://api.github.com/users/nodejs/gists{/gist_id}","starred_url":"https://api.github.com/users/nodejs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nodejs/subscriptions","organizations_url":"https://api.github.com/users/nodejs/orgs","repos_url":"https://api.github.com/users/nodejs/repos","events_url":"https://api.github.com/users/nodejs/events{/privacy}","received_events_url":"https://api.github.com/users/nodejs/received_events","type":"Organization","site_admin":false},"repo":{"id":48204104,"name":"node-eps","full_name":"nodejs/node-eps","owner":{"login":"nodejs","id":9950313,"avatar_url":"https://avatars.githubusercontent.com/u/9950313?v=3","gravatar_id":"","url":"https://api.github.com/users/nodejs","html_url":"https://github.com/nodejs","followers_url":"https://api.github.com/users/nodejs/followers","following_url":"https://api.github.com/users/nodejs/following{/other_user}","gists_url":"https://api.github.com/users/nodejs/gists{/gist_id}","starred_url":"https://api.github.com/users/nodejs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nodejs/subscriptions","organizations_url":"https://api.github.com/users/nodejs/orgs","repos_url":"https://api.github.com/users/nodejs/repos","events_url":"https://api.github.com/users/nodejs/events{/privacy}","received_events_url":"https://api.github.com/users/nodejs/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/nodejs/node-eps","description":"Node.js Enhancement Proposals for discussion on future API additions/changes to Node core","fork":false,"url":"https://api.github.com/repos/nodejs/node-eps","forks_url":"https://api.github.com/repos/nodejs/node-eps/forks","keys_url":"https://api.github.com/repos/nodejs/node-eps/keys{/key_id}","collaborators_url":"https://api.github.com/repos/nodejs/node-eps/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/nodejs/node-eps/teams","hooks_url":"https://api.github.com/repos/nodejs/node-eps/hooks","issue_events_url":"https://api.github.com/repos/nodejs/node-eps/issues/events{/number}","events_url":"https://api.github.com/repos/nodejs/node-eps/events","assignees_url":"https://api.github.com/repos/nodejs/node-eps/assignees{/user}","branches_url":"https://api.github.com/repos/nodejs/node-eps/branches{/branch}","tags_url":"https://api.github.com/repos/nodejs/node-eps/tags","blobs_url":"https://api.github.com/repos/nodejs/node-eps/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/nodejs/node-eps/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/nodejs/node-eps/git/refs{/sha}","trees_url":"https://api.github.com/repos/nodejs/node-eps/git/trees{/sha}","statuses_url":"https://api.github.com/repos/nodejs/node-eps/statuses/{sha}","languages_url":"https://api.github.com/repos/nodejs/node-eps/languages","stargazers_url":"https://api.github.com/repos/nodejs/node-eps/stargazers","contributors_url":"https://api.github.com/repos/nodejs/node-eps/contributors","subscribers_url":"https://api.github.com/repos/nodejs/node-eps/subscribers","subscription_url":"https://api.github.com/repos/nodejs/node-eps/subscription","commits_url":"https://api.github.com/repos/nodejs/node-eps/commits{/sha}","git_commits_url":"https://api.github.com/repos/nodejs/node-eps/git/commits{/sha}","comments_url":"https://api.github.com/repos/nodejs/node-eps/comments{/number}","issue_comment_url":"https://api.github.com/repos/nodejs/node-eps/issues/comments{/number}","contents_url":"https://api.github.com/repos/nodejs/node-eps/contents/{+path}","compare_url":"https://api.github.com/repos/nodejs/node-eps/compare/{base}...{head}","merges_url":"https://api.github.com/repos/nodejs/node-eps/merges","archive_url":"https://api.github.com/repos/nodejs/node-eps/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/nodejs/node-eps/downloads","issues_url":"https://api.github.com/repos/nodejs/node-eps/issues{/number}","pulls_url":"https://api.github.com/repos/nodejs/node-eps/pulls{/number}","milestones_url":"https://api.github.com/repos/nodejs/node-eps/milestones{/number}","notifications_url":"https://api.github.com/repos/nodejs/node-eps/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/nodejs/node-eps/labels{/name}","releases_url":"https://api.github.com/repos/nodejs/node-eps/releases{/id}","deployments_url":"https://api.github.com/repos/nodejs/node-eps/deployments","created_at":"2015-12-17T23:39:07Z","updated_at":"2016-08-23T08:59:58Z","pushed_at":"2016-08-25T18:51:21Z","git_url":"git://github.com/nodejs/node-eps.git","ssh_url":"git@github.com:nodejs/node-eps.git","clone_url":"https://github.com/nodejs/node-eps.git","svn_url":"https://github.com/nodejs/node-eps","homepage":null,"size":148,"stargazers_count":159,"watchers_count":159,"language":null,"has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":22,"mirror_url":null,"open_issues_count":18,"forks":22,"open_issues":18,"watchers":159,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/nodejs/node-eps/pulls/18"},"html":{"href":"https://github.com/nodejs/node-eps/pull/18"},"issue":{"href":"https://api.github.com/repos/nodejs/node-eps/issues/18"},"comments":{"href":"https://api.github.com/repos/nodejs/node-eps/issues/18/comments"},"review_comments":{"href":"https://api.github.com/repos/nodejs/node-eps/pulls/18/comments"},"review_comment":{"href":"https://api.github.com/repos/nodejs/node-eps/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/nodejs/node-eps/pulls/18/commits"},"statuses":{"href":"https://api.github.com/repos/nodejs/node-eps/statuses/217353e033b816360b77a43ce6f4eb62010f7e49"}}}}
{"actor":{"display_login":"AndreasMadsen"}}