{"action":"created","comment":{"url":"https://api.github.com/repos/globality-corp/nodule-graphql/pulls/comments/185654913","pull_request_review_id":117088668,"id":185654913,"diff_hunk":"@@ -0,0 +1,145 @@\n+import { flatten, indexOf, isFunction } from 'lodash';\n+import { getContainer } from '@globality/nodule-config';\n+\n+\n+/* Expand a key either into itself (if it's a string) or via a function call.\n+ *\n+ * Allowing keys to be functions allows resolver selection to be dynamic,\n+ * based on runtime arguments.\n+ */\n+function expandKey(key, ...args) {\n+    return isFunction(key) ? key(...args) : key;\n+}\n+\n+\n+/* Build a mapping from keys to DI members with the given type.\n+ */\n+function buildMapping(keys, type) {\n+    return keys.map(\n+        key => [key, getContainer(`graphql.${type}.${key}`)],\n+    ).filter(\n+        pair => !!pair[1],\n+    ).reduce(\n+        (acc, [key, value]) => Object.assign(\n+            acc,\n+            {\n+                [key]: value,\n+            },\n+        ),\n+        {},\n+    );\n+}\n+\n+\n+/* Execute a pipeline.\n+ */\n+async function executePipeline(pipeline, args) {\n+    // we expect exactly one resolver\n+    const resolverKey = Object.keys(pipeline.resolvers)[0];\n+    const resolverIndex = indexOf(pipeline.keys, resolverKey);\n+    const resolver = pipeline.resolvers[resolverKey];\n+\n+    // apply masks to the input arguments\n+    const masked = pipeline.keys.filter(\n+        (key, index) => index < resolverIndex,\n+    ).reduce(\n+        (acc, key) => pipeline.masks[key](...acc),\n+        args,\n+    );\n+\n+    // apply the resolver to the masked input\n+    const result = await resolver.resolve(...masked);\n+\n+    // transform the result and the masked input\n+    return pipeline.keys.filter(\n+        (key, index) => index > resolverIndex,\n+    ).reduce(\n+        (acc, key) => pipeline.transforms[key](acc, ...masked),\n+        result,\n+    );\n+}\n+\n+\n+/* Validate a pipeline.\n+ */\n+function validatePipeline(pipeline) {\n+    // Ensure we have *exactly* one resolver\n+    if (Object.keys(pipeline.resolvers).length === 0) {\n+        throw new Error(`No resolver could be found for: ${pipeline.keys.join(', ')}`);\n+    }\n+    if (Object.keys(pipeline.resolvers).length !== 1) {\n+        throw new Error(`Multiple resolvers were found for: ${pipeline.keys.join(', ')}`);\n+    }\n+\n+    const resolverKey = Object.keys(pipeline.resolvers)[0];\n+    const resolverIndex = indexOf(pipeline.keys, resolverKey);\n+\n+    // Ensure all keys at the front of the pipeline are valid mask functions\n+    pipeline.keys.filter(\n+        (key, index) => index < resolverIndex,\n+    ).forEach(\n+        (key) => {\n+            if (!pipeline.masks[key]) {\n+                throw new Error(`No mask could be found for: ${key}`);\n+            }\n+        },\n+    );\n+\n+    // Ensure that all keys at the back of the pipeline are valid transforms\n+    pipeline.keys.filter(\n+        (key, index) => index > resolverIndex,\n+    ).forEach(\n+        (key) => {\n+            if (!pipeline.transforms[key]) {\n+                throw new Error(`No transform could be found for: ${key}`);\n+            }\n+        },\n+    );\n+}\n+\n+\n+/* Define a pipeline of functions that make up a single logical resolver.\n+ *\n+ * Exactly one of these functions is expected to be a proper resolver; the\n+ * other functions should be supplemental `mask` or `transform` functions,\n+ * allowing a single resolver instance to be reused in different contexts\n+ * without copying.\n+ *\n+ * Example resource definition:\n+ *\n+ *     resolve: getResolverPipeline('maskFunctionName', 'resolverName', 'transformName'),\n+ */\n+export function getResolverPipeline(...keys) {\n+    if (!keys.length) {\n+        throw new Error('Resolver pipeline requires a leeast one input');","path":"src/resolvers/pipeline.js","position":114,"original_position":114,"commit_id":"d0c8a6eba7583b12eb927727b90d00162c383c13","original_commit_id":"d0c8a6eba7583b12eb927727b90d00162c383c13","user":{"login":"netama","id":18385486,"avatar_url":"https://avatars3.githubusercontent.com/u/18385486?v=4","gravatar_id":"","url":"https://api.github.com/users/netama","html_url":"https://github.com/netama","followers_url":"https://api.github.com/users/netama/followers","following_url":"https://api.github.com/users/netama/following{/other_user}","gists_url":"https://api.github.com/users/netama/gists{/gist_id}","starred_url":"https://api.github.com/users/netama/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/netama/subscriptions","organizations_url":"https://api.github.com/users/netama/orgs","repos_url":"https://api.github.com/users/netama/repos","events_url":"https://api.github.com/users/netama/events{/privacy}","received_events_url":"https://api.github.com/users/netama/received_events","type":"User","site_admin":false},"body":"nit: `leeast`","created_at":"2018-05-02T22:15:17Z","updated_at":"2018-05-02T22:15:17Z","html_url":"https://github.com/globality-corp/nodule-graphql/pull/39#discussion_r185654913","pull_request_url":"https://api.github.com/repos/globality-corp/nodule-graphql/pulls/39","author_association":"NONE","_links":{"self":{"href":"https://api.github.com/repos/globality-corp/nodule-graphql/pulls/comments/185654913"},"html":{"href":"https://github.com/globality-corp/nodule-graphql/pull/39#discussion_r185654913"},"pull_request":{"href":"https://api.github.com/repos/globality-corp/nodule-graphql/pulls/39"}}},"pull_request":{"url":"https://api.github.com/repos/globality-corp/nodule-graphql/pulls/39","id":185573143,"html_url":"https://github.com/globality-corp/nodule-graphql/pull/39","diff_url":"https://github.com/globality-corp/nodule-graphql/pull/39.diff","patch_url":"https://github.com/globality-corp/nodule-graphql/pull/39.patch","issue_url":"https://api.github.com/repos/globality-corp/nodule-graphql/issues/39","number":39,"state":"open","locked":false,"title":"Enable resolver pipelines.","user":{"login":"jessemyers","id":477722,"avatar_url":"https://avatars0.githubusercontent.com/u/477722?v=4","gravatar_id":"","url":"https://api.github.com/users/jessemyers","html_url":"https://github.com/jessemyers","followers_url":"https://api.github.com/users/jessemyers/followers","following_url":"https://api.github.com/users/jessemyers/following{/other_user}","gists_url":"https://api.github.com/users/jessemyers/gists{/gist_id}","starred_url":"https://api.github.com/users/jessemyers/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jessemyers/subscriptions","organizations_url":"https://api.github.com/users/jessemyers/orgs","repos_url":"https://api.github.com/users/jessemyers/repos","events_url":"https://api.github.com/users/jessemyers/events{/privacy}","received_events_url":"https://api.github.com/users/jessemyers/received_events","type":"User","site_admin":false},"body":"Resolvers have accumulated several sub-functions, including `mask` functions (which\r\nmanipulate inputs) and `transform` functions (which manipulate outputs). These concepts\r\nwork well in the context of a single resolver, but fall short when reusing \"core\"\r\nresolvers in multiple contexts with slight differences.\r\n\r\nIn these cases, we want the ability to \"attach\" custom `mask` and `transform` functions\r\nto an existing resolver. The mechanism we use is a *pipeline* compose of a list of keys\r\n(or key functions) that lookup a mask, resolver, or transform function in the DI container.\r\n\r\nFor example, this pipeline:\r\n\r\n```\r\ngetResolverPipeline('extractCompanyId', 'company.retrieve', 'toList')\r\n```\r\n\r\nWould most likely:\r\n -  Apply a mask to the input `obj` value to fetch a `companyId`\r\n -  Invoke the company retrieve resolver to get a company\r\n -  Transform the company into list form\r\n\r\nMeaning that:\r\n -  Company retrieval can focus on doing a single job\r\n -  List transformation can be reused","created_at":"2018-05-02T22:02:32Z","updated_at":"2018-05-02T22:15:17Z","closed_at":null,"merged_at":null,"merge_commit_sha":"785ee869e99d3f6c69bb4ec41503e48b7a5564a5","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/globality-corp/nodule-graphql/pulls/39/commits","review_comments_url":"https://api.github.com/repos/globality-corp/nodule-graphql/pulls/39/comments","review_comment_url":"https://api.github.com/repos/globality-corp/nodule-graphql/pulls/comments{/number}","comments_url":"https://api.github.com/repos/globality-corp/nodule-graphql/issues/39/comments","statuses_url":"https://api.github.com/repos/globality-corp/nodule-graphql/statuses/d0c8a6eba7583b12eb927727b90d00162c383c13","head":{"label":"globality-corp:feature/resolver-pipelines","ref":"feature/resolver-pipelines","sha":"d0c8a6eba7583b12eb927727b90d00162c383c13","user":{"login":"globality-corp","id":11172782,"avatar_url":"https://avatars2.githubusercontent.com/u/11172782?v=4","gravatar_id":"","url":"https://api.github.com/users/globality-corp","html_url":"https://github.com/globality-corp","followers_url":"https://api.github.com/users/globality-corp/followers","following_url":"https://api.github.com/users/globality-corp/following{/other_user}","gists_url":"https://api.github.com/users/globality-corp/gists{/gist_id}","starred_url":"https://api.github.com/users/globality-corp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/globality-corp/subscriptions","organizations_url":"https://api.github.com/users/globality-corp/orgs","repos_url":"https://api.github.com/users/globality-corp/repos","events_url":"https://api.github.com/users/globality-corp/events{/privacy}","received_events_url":"https://api.github.com/users/globality-corp/received_events","type":"Organization","site_admin":false},"repo":{"id":125918357,"name":"nodule-graphql","full_name":"globality-corp/nodule-graphql","owner":{"login":"globality-corp","id":11172782,"avatar_url":"https://avatars2.githubusercontent.com/u/11172782?v=4","gravatar_id":"","url":"https://api.github.com/users/globality-corp","html_url":"https://github.com/globality-corp","followers_url":"https://api.github.com/users/globality-corp/followers","following_url":"https://api.github.com/users/globality-corp/following{/other_user}","gists_url":"https://api.github.com/users/globality-corp/gists{/gist_id}","starred_url":"https://api.github.com/users/globality-corp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/globality-corp/subscriptions","organizations_url":"https://api.github.com/users/globality-corp/orgs","repos_url":"https://api.github.com/users/globality-corp/repos","events_url":"https://api.github.com/users/globality-corp/events{/privacy}","received_events_url":"https://api.github.com/users/globality-corp/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/globality-corp/nodule-graphql","description":"Node GraphQL Conventions","fork":false,"url":"https://api.github.com/repos/globality-corp/nodule-graphql","forks_url":"https://api.github.com/repos/globality-corp/nodule-graphql/forks","keys_url":"https://api.github.com/repos/globality-corp/nodule-graphql/keys{/key_id}","collaborators_url":"https://api.github.com/repos/globality-corp/nodule-graphql/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/globality-corp/nodule-graphql/teams","hooks_url":"https://api.github.com/repos/globality-corp/nodule-graphql/hooks","issue_events_url":"https://api.github.com/repos/globality-corp/nodule-graphql/issues/events{/number}","events_url":"https://api.github.com/repos/globality-corp/nodule-graphql/events","assignees_url":"https://api.github.com/repos/globality-corp/nodule-graphql/assignees{/user}","branches_url":"https://api.github.com/repos/globality-corp/nodule-graphql/branches{/branch}","tags_url":"https://api.github.com/repos/globality-corp/nodule-graphql/tags","blobs_url":"https://api.github.com/repos/globality-corp/nodule-graphql/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/globality-corp/nodule-graphql/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/globality-corp/nodule-graphql/git/refs{/sha}","trees_url":"https://api.github.com/repos/globality-corp/nodule-graphql/git/trees{/sha}","statuses_url":"https://api.github.com/repos/globality-corp/nodule-graphql/statuses/{sha}","languages_url":"https://api.github.com/repos/globality-corp/nodule-graphql/languages","stargazers_url":"https://api.github.com/repos/globality-corp/nodule-graphql/stargazers","contributors_url":"https://api.github.com/repos/globality-corp/nodule-graphql/contributors","subscribers_url":"https://api.github.com/repos/globality-corp/nodule-graphql/subscribers","subscription_url":"https://api.github.com/repos/globality-corp/nodule-graphql/subscription","commits_url":"https://api.github.com/repos/globality-corp/nodule-graphql/commits{/sha}","git_commits_url":"https://api.github.com/repos/globality-corp/nodule-graphql/git/commits{/sha}","comments_url":"https://api.github.com/repos/globality-corp/nodule-graphql/comments{/number}","issue_comment_url":"https://api.github.com/repos/globality-corp/nodule-graphql/issues/comments{/number}","contents_url":"https://api.github.com/repos/globality-corp/nodule-graphql/contents/{+path}","compare_url":"https://api.github.com/repos/globality-corp/nodule-graphql/compare/{base}...{head}","merges_url":"https://api.github.com/repos/globality-corp/nodule-graphql/merges","archive_url":"https://api.github.com/repos/globality-corp/nodule-graphql/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/globality-corp/nodule-graphql/downloads","issues_url":"https://api.github.com/repos/globality-corp/nodule-graphql/issues{/number}","pulls_url":"https://api.github.com/repos/globality-corp/nodule-graphql/pulls{/number}","milestones_url":"https://api.github.com/repos/globality-corp/nodule-graphql/milestones{/number}","notifications_url":"https://api.github.com/repos/globality-corp/nodule-graphql/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/globality-corp/nodule-graphql/labels{/name}","releases_url":"https://api.github.com/repos/globality-corp/nodule-graphql/releases{/id}","deployments_url":"https://api.github.com/repos/globality-corp/nodule-graphql/deployments","created_at":"2018-03-19T20:48:03Z","updated_at":"2018-05-01T20:28:32Z","pushed_at":"2018-05-02T22:02:33Z","git_url":"git://github.com/globality-corp/nodule-graphql.git","ssh_url":"git@github.com:globality-corp/nodule-graphql.git","clone_url":"https://github.com/globality-corp/nodule-graphql.git","svn_url":"https://github.com/globality-corp/nodule-graphql","homepage":"","size":182,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":true,"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":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit"},"forks":0,"open_issues":1,"watchers":0,"default_branch":"develop"}},"base":{"label":"globality-corp:develop","ref":"develop","sha":"94d1eade43261c6efbf9185046f811d0d3ed1945","user":{"login":"globality-corp","id":11172782,"avatar_url":"https://avatars2.githubusercontent.com/u/11172782?v=4","gravatar_id":"","url":"https://api.github.com/users/globality-corp","html_url":"https://github.com/globality-corp","followers_url":"https://api.github.com/users/globality-corp/followers","following_url":"https://api.github.com/users/globality-corp/following{/other_user}","gists_url":"https://api.github.com/users/globality-corp/gists{/gist_id}","starred_url":"https://api.github.com/users/globality-corp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/globality-corp/subscriptions","organizations_url":"https://api.github.com/users/globality-corp/orgs","repos_url":"https://api.github.com/users/globality-corp/repos","events_url":"https://api.github.com/users/globality-corp/events{/privacy}","received_events_url":"https://api.github.com/users/globality-corp/received_events","type":"Organization","site_admin":false},"repo":{"id":125918357,"name":"nodule-graphql","full_name":"globality-corp/nodule-graphql","owner":{"login":"globality-corp","id":11172782,"avatar_url":"https://avatars2.githubusercontent.com/u/11172782?v=4","gravatar_id":"","url":"https://api.github.com/users/globality-corp","html_url":"https://github.com/globality-corp","followers_url":"https://api.github.com/users/globality-corp/followers","following_url":"https://api.github.com/users/globality-corp/following{/other_user}","gists_url":"https://api.github.com/users/globality-corp/gists{/gist_id}","starred_url":"https://api.github.com/users/globality-corp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/globality-corp/subscriptions","organizations_url":"https://api.github.com/users/globality-corp/orgs","repos_url":"https://api.github.com/users/globality-corp/repos","events_url":"https://api.github.com/users/globality-corp/events{/privacy}","received_events_url":"https://api.github.com/users/globality-corp/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/globality-corp/nodule-graphql","description":"Node GraphQL Conventions","fork":false,"url":"https://api.github.com/repos/globality-corp/nodule-graphql","forks_url":"https://api.github.com/repos/globality-corp/nodule-graphql/forks","keys_url":"https://api.github.com/repos/globality-corp/nodule-graphql/keys{/key_id}","collaborators_url":"https://api.github.com/repos/globality-corp/nodule-graphql/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/globality-corp/nodule-graphql/teams","hooks_url":"https://api.github.com/repos/globality-corp/nodule-graphql/hooks","issue_events_url":"https://api.github.com/repos/globality-corp/nodule-graphql/issues/events{/number}","events_url":"https://api.github.com/repos/globality-corp/nodule-graphql/events","assignees_url":"https://api.github.com/repos/globality-corp/nodule-graphql/assignees{/user}","branches_url":"https://api.github.com/repos/globality-corp/nodule-graphql/branches{/branch}","tags_url":"https://api.github.com/repos/globality-corp/nodule-graphql/tags","blobs_url":"https://api.github.com/repos/globality-corp/nodule-graphql/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/globality-corp/nodule-graphql/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/globality-corp/nodule-graphql/git/refs{/sha}","trees_url":"https://api.github.com/repos/globality-corp/nodule-graphql/git/trees{/sha}","statuses_url":"https://api.github.com/repos/globality-corp/nodule-graphql/statuses/{sha}","languages_url":"https://api.github.com/repos/globality-corp/nodule-graphql/languages","stargazers_url":"https://api.github.com/repos/globality-corp/nodule-graphql/stargazers","contributors_url":"https://api.github.com/repos/globality-corp/nodule-graphql/contributors","subscribers_url":"https://api.github.com/repos/globality-corp/nodule-graphql/subscribers","subscription_url":"https://api.github.com/repos/globality-corp/nodule-graphql/subscription","commits_url":"https://api.github.com/repos/globality-corp/nodule-graphql/commits{/sha}","git_commits_url":"https://api.github.com/repos/globality-corp/nodule-graphql/git/commits{/sha}","comments_url":"https://api.github.com/repos/globality-corp/nodule-graphql/comments{/number}","issue_comment_url":"https://api.github.com/repos/globality-corp/nodule-graphql/issues/comments{/number}","contents_url":"https://api.github.com/repos/globality-corp/nodule-graphql/contents/{+path}","compare_url":"https://api.github.com/repos/globality-corp/nodule-graphql/compare/{base}...{head}","merges_url":"https://api.github.com/repos/globality-corp/nodule-graphql/merges","archive_url":"https://api.github.com/repos/globality-corp/nodule-graphql/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/globality-corp/nodule-graphql/downloads","issues_url":"https://api.github.com/repos/globality-corp/nodule-graphql/issues{/number}","pulls_url":"https://api.github.com/repos/globality-corp/nodule-graphql/pulls{/number}","milestones_url":"https://api.github.com/repos/globality-corp/nodule-graphql/milestones{/number}","notifications_url":"https://api.github.com/repos/globality-corp/nodule-graphql/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/globality-corp/nodule-graphql/labels{/name}","releases_url":"https://api.github.com/repos/globality-corp/nodule-graphql/releases{/id}","deployments_url":"https://api.github.com/repos/globality-corp/nodule-graphql/deployments","created_at":"2018-03-19T20:48:03Z","updated_at":"2018-05-01T20:28:32Z","pushed_at":"2018-05-02T22:02:33Z","git_url":"git://github.com/globality-corp/nodule-graphql.git","ssh_url":"git@github.com:globality-corp/nodule-graphql.git","clone_url":"https://github.com/globality-corp/nodule-graphql.git","svn_url":"https://github.com/globality-corp/nodule-graphql","homepage":"","size":182,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":true,"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":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit"},"forks":0,"open_issues":1,"watchers":0,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/globality-corp/nodule-graphql/pulls/39"},"html":{"href":"https://github.com/globality-corp/nodule-graphql/pull/39"},"issue":{"href":"https://api.github.com/repos/globality-corp/nodule-graphql/issues/39"},"comments":{"href":"https://api.github.com/repos/globality-corp/nodule-graphql/issues/39/comments"},"review_comments":{"href":"https://api.github.com/repos/globality-corp/nodule-graphql/pulls/39/comments"},"review_comment":{"href":"https://api.github.com/repos/globality-corp/nodule-graphql/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/globality-corp/nodule-graphql/pulls/39/commits"},"statuses":{"href":"https://api.github.com/repos/globality-corp/nodule-graphql/statuses/d0c8a6eba7583b12eb927727b90d00162c383c13"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"netama"}}