From dec4839100ef69a20a75c0938116ccf19dcc6179 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2020 20:13:56 +0000 Subject: [PATCH 01/10] chore(deps): update dependency gatsby-theme-apollo-docs to v4.0.7 (#3827) Co-authored-by: WhiteSource Renovate --- docs/package-lock.json | 458 +++++++++++++++++++++++++++++------------ docs/package.json | 2 +- 2 files changed, 326 insertions(+), 134 deletions(-) diff --git a/docs/package-lock.json b/docs/package-lock.json index 2d87c66cc51..a6033cf6067 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -1375,11 +1375,11 @@ } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.8.0.tgz", - "integrity": "sha512-SjJ2ZXCylpWC+5DTES0/pbpNmw/FnjU/3dF068xF0DU9aN+oOKah+3MCSFcb4pnZ9IwmxfOy4KnbGJSQR+hAZA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-8qvuPwU/xxUCt78HocNlv0mXXo0wdh9VT1R04WU8HGOfaOob26pF+9P5/lYjN/q7DHOX1bvX60hnhOvuQUJdbA==", "requires": { - "@babel/helper-plugin-utils": "^7.8.0", + "@babel/helper-plugin-utils": "^7.8.3", "@babel/plugin-syntax-object-rest-spread": "^7.8.0" }, "dependencies": { @@ -1457,11 +1457,11 @@ } }, "@babel/plugin-syntax-jsx": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.8.0.tgz", - "integrity": "sha512-zLDUckAuKeOtxJhfNE0TlR7iEApb2u7EYRlh5cxKzq6A5VzUbYEdyJGJlug41jDbjRbHTtsLKZUnUcy/8V3xZw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.8.3.tgz", + "integrity": "sha512-WxdW9xyLgBdefoo0Ynn3MRSkhe5tFVxxKNVdnZSh318WrG2e2jH+E9wd/++JsqcLJZPfz87njQJ8j2Upjm0M0A==", "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.8.3" }, "dependencies": { "@babel/helper-plugin-utils": { @@ -2530,54 +2530,122 @@ } }, "@mdx-js/mdx": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-1.5.5.tgz", - "integrity": "sha512-Xv1lJ+VWt8giWQrqf4GdIBxl08SfepfIWAnuuIzuR+wA59SaXDvkW6XFIvl8u495OQEB1eugMvq8l2XR8ZGr1A==", - "requires": { - "@babel/core": "7.8.0", - "@babel/plugin-syntax-jsx": "7.8.0", - "@babel/plugin-syntax-object-rest-spread": "7.8.0", - "@mdx-js/util": "^1.5.5", - "babel-plugin-apply-mdx-type-prop": "^1.5.5", - "babel-plugin-extract-import-names": "^1.5.5", + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-1.5.7.tgz", + "integrity": "sha512-db1E3P0HCgSUX768Y/jIcr5h41VR5AsvaOmPTydltNM4R8Uh863IqDvnkpa7l829bY/tp6wrMBWM2NH0oLuxHw==", + "requires": { + "@babel/core": "7.8.4", + "@babel/plugin-syntax-jsx": "7.8.3", + "@babel/plugin-syntax-object-rest-spread": "7.8.3", + "@mdx-js/util": "^1.5.7", + "babel-plugin-apply-mdx-type-prop": "^1.5.7", + "babel-plugin-extract-import-names": "^1.5.7", "camelcase-css": "2.0.1", - "detab": "2.0.2", + "detab": "2.0.3", "hast-util-raw": "5.0.1", "lodash.uniq": "4.5.0", - "mdast-util-to-hast": "6.0.2", - "remark-mdx": "^1.5.5", + "mdast-util-to-hast": "7.0.0", + "remark-mdx": "^1.5.7", "remark-parse": "7.0.2", "remark-squeeze-paragraphs": "3.0.4", "style-to-object": "0.3.0", "unified": "8.4.2", - "unist-builder": "1.0.4", - "unist-util-visit": "2.0.1" + "unist-builder": "2.0.3", + "unist-util-visit": "2.0.2" }, "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/core": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.4.tgz", + "integrity": "sha512-0LiLrB2PwrVI+a2/IEskBopDYSd8BCb3rOvH7D5tzoWd696TBEduBvuLVm4Nx6rltrLZqvI3MCalB2K2aVzQjA==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.4", + "@babel/helpers": "^7.8.4", + "@babel/parser": "^7.8.4", + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.8.4", + "@babel/types": "^7.8.3", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.0", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } + }, "@babel/helper-plugin-utils": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" }, + "@babel/helpers": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.8.4.tgz", + "integrity": "sha512-VPbe7wcQ4chu4TDQjimHv/5tj73qz88o12EPkO2ValS2QiQS/1F2SsjyIGNnAD0vF/nZS6Cf9i+vW6HIlnaR8w==", + "requires": { + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.8.4", + "@babel/types": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.0.tgz", - "integrity": "sha512-dt89fDlkfkTrQcy5KavMQPyF2A6tR0kYp8HAnIoQv5hO34iAUffHghP/hMGd7Gf/+uYTmLQO0ar7peX1SUWyIA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "requires": { "@babel/helper-plugin-utils": "^7.8.0" } + }, + "@babel/types": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } } } }, "@mdx-js/react": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-1.5.5.tgz", - "integrity": "sha512-Qwvri4zyU9ZbhhXsH0wfSZ/J9b8mARRTB6GSCTnyKRffO2CaQXl9oLsvRAeQSLRei/onEARc+RexH+jMeNS1rw==" + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-1.5.7.tgz", + "integrity": "sha512-OxX/GKyVlqY7WqyRcsIA/qr7i1Xq3kAVNUhSSnL1mfKKNKO+hwMWcZX4WS2OItLtoavA2/8TVDHpV/MWKWyfvw==" }, "@mdx-js/util": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@mdx-js/util/-/util-1.5.5.tgz", - "integrity": "sha512-IudQkyZuM8T1CrSX9r0ShPXCABjtEtyrV4lxQqhKAwFqw1aYpy/5LOZhitMLoJTybZPVdPotuh+zjqYy9ZOSbA==" + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/@mdx-js/util/-/util-1.5.7.tgz", + "integrity": "sha512-SV+V8A+Y33pmVT/LWk/2y51ixIyA/QH1XL+nrWAhoqre1rFtxOEZ4jr0W+bKZpeahOvkn/BQTheK+dRty9o/ig==" }, "@mikaelkristiansson/domready": { "version": "1.0.10", @@ -3847,18 +3915,18 @@ } }, "babel-plugin-apply-mdx-type-prop": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/babel-plugin-apply-mdx-type-prop/-/babel-plugin-apply-mdx-type-prop-1.5.5.tgz", - "integrity": "sha512-yaklz3xE5vFtZpPpYC9lDbTqlC6hq0CjgheiLw3i40lY8vG0DINh+HJ7rq1Gi1g0q/iihwetJ+YFGpUM4YXAGA==", + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/babel-plugin-apply-mdx-type-prop/-/babel-plugin-apply-mdx-type-prop-1.5.7.tgz", + "integrity": "sha512-SUDwTmMmxzaAZ1YfAPnL2UI3q/JEs+fekx/QTZYEgK+cVGMwS/PrCeK9UDlTHOYJr9b4mieR+iLhm43jrav2WA==", "requires": { - "@babel/helper-plugin-utils": "7.8.0", - "@mdx-js/util": "^1.5.5" + "@babel/helper-plugin-utils": "7.8.3", + "@mdx-js/util": "^1.5.7" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.0.tgz", - "integrity": "sha512-+hAlRGdf8fHQAyNnDBqTHQhwdLURLdrCROoWaEQYiQhk2sV9Rhs+GoFZZfMJExTq9HG8o2NX3uN2G90bFtmFdA==" + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" } } }, @@ -3888,17 +3956,17 @@ } }, "babel-plugin-extract-import-names": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/babel-plugin-extract-import-names/-/babel-plugin-extract-import-names-1.5.5.tgz", - "integrity": "sha512-F9paxnUtO3vddyOX+vbRa8KrkuovJIFB8KmB/dEICqTUm2331LcGbjCKzZApOri4Igbk9MnYybm2fDsuPJC3vA==", + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/babel-plugin-extract-import-names/-/babel-plugin-extract-import-names-1.5.7.tgz", + "integrity": "sha512-kZX4g9ehTyxjdbq2rb8wW307+jNu5z3KllYs8cnbapSwclT9wBErJoqvKKZAkuiaufp0r+7WaIvjhKtJ7QlG3A==", "requires": { - "@babel/helper-plugin-utils": "7.8.0" + "@babel/helper-plugin-utils": "7.8.3" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.0.tgz", - "integrity": "sha512-+hAlRGdf8fHQAyNnDBqTHQhwdLURLdrCROoWaEQYiQhk2sV9Rhs+GoFZZfMJExTq9HG8o2NX3uN2G90bFtmFdA==" + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" } } }, @@ -5575,9 +5643,9 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, "copy-to-clipboard": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.2.1.tgz", - "integrity": "sha512-btru1Q6RD9wbonIvEU5EfnhIRGHLo//BGXQ1hNAD2avIs/nBZlpbOeKtv3mhoUByN4DB9Cb6/vXBymj1S43KmA==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", + "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==", "requires": { "toggle-selection": "^1.0.6" } @@ -6532,9 +6600,9 @@ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, "detab": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detab/-/detab-2.0.2.tgz", - "integrity": "sha512-Q57yPrxScy816TTE1P/uLRXLDKjXhvYTbfxS/e6lPD+YrqghbsMlGB9nQzj/zVtSPaF0DFPSdO916EWO4sQUyQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detab/-/detab-2.0.3.tgz", + "integrity": "sha512-Up8P0clUVwq0FnFjDclzZsy9PadzRn5FFxrr47tQQvMHqyiFYVbpH8oXDzWtF0Q7pYy3l+RPmtBl+BsFF6wH0A==", "requires": { "repeat-string": "^1.5.4" } @@ -9957,9 +10025,9 @@ } }, "gatsby-theme-apollo-docs": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/gatsby-theme-apollo-docs/-/gatsby-theme-apollo-docs-4.0.6.tgz", - "integrity": "sha512-KEwQ6KwpuqnBnNb+MUACh7AevsDDWnxaN8lHHur6t9EBAuQ1s/k2RYzi2UhFTsCYEbKg0bo2qM+qz4zvSeo0pA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/gatsby-theme-apollo-docs/-/gatsby-theme-apollo-docs-4.0.7.tgz", + "integrity": "sha512-gJSp5GFdwdpzZhRq7l1FoQSB7QociYO/b18mcqQ2EQpvZUqW/STBk+ZD1oOp4Qq4xq/mli/KY48vcXXid+vEZA==", "requires": { "@mdx-js/mdx": "^1.1.0", "@mdx-js/react": "^1.0.27", @@ -9990,9 +10058,9 @@ } }, "gatsby-transformer-remark": { - "version": "2.6.52", - "resolved": "https://registry.npmjs.org/gatsby-transformer-remark/-/gatsby-transformer-remark-2.6.52.tgz", - "integrity": "sha512-G0Fw2ZHFMiC1KdIR31NSZcQ3+KtghVfWK8FmiExMmN8bZQROyuqrjv1DqhUgg2yChHZKZ14H3hTnVmvCv48shg==", + "version": "2.6.53", + "resolved": "https://registry.npmjs.org/gatsby-transformer-remark/-/gatsby-transformer-remark-2.6.53.tgz", + "integrity": "sha512-Gg5d93B20cE0xp5q3ieuVwn4CaYDBBImw1SpQCySixUo43yjdQIWfwkSePYDaKNPzaICdRyy/7+X2bbZP43e5w==", "requires": { "@babel/runtime": "^7.7.6", "bluebird": "^3.7.2", @@ -10112,9 +10180,9 @@ } }, "mdast-util-toc": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-toc/-/mdast-util-toc-5.0.1.tgz", - "integrity": "sha512-icdxfPSLPJuK0Qd7w2+X9qcsPl9fVdpiEAjhS4zBp7WbNhpkorodXPUG0ZG4N5E++q4+IidV2boJpcCF985xlA==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-toc/-/mdast-util-toc-5.0.2.tgz", + "integrity": "sha512-IeihbQLXrnCs/427dVzCp3ffvSPpdx/Mc2WWYAdVaS+MFqdKZHlJylGWAA1cGPewhEVyITsWrlXJ/b2d80Wsnw==", "requires": { "@types/mdast": "^3.0.3", "@types/unist": "^2.0.3", @@ -10202,6 +10270,14 @@ "x-is-string": "^0.1.0" } }, + "unist-builder": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-1.0.4.tgz", + "integrity": "sha512-v6xbUPP7ILrT15fHGrNyHc1Xda8H3xVhP7/HAIotHOhVPjH5dCXA097C3Rry1Q2O+HbOLCao4hfPB+EYEjHgVg==", + "requires": { + "object-assign": "^4.1.0" + } + }, "unist-util-is": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-2.1.3.tgz", @@ -10292,9 +10368,9 @@ } }, "github-slugger": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.2.1.tgz", - "integrity": "sha512-SsZUjg/P03KPzQBt7OxJPasGw6NRO5uOgiZ5RGXVud5iSIZ0eNZeNp5rTwCxtavrRUa/A77j8mePVc5lEvk0KQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.3.0.tgz", + "integrity": "sha512-gwJScWVNhFYSRDvURk/8yhcFBee6aFjye2a7Lhb2bUyRulpIoek9p0I9Kt7PT67d/nUlZbFu8L9RLiA0woQN8Q==", "requires": { "emoji-regex": ">=6.0.0 <=6.1.1" }, @@ -11723,9 +11799,9 @@ } }, "is-alphabetical": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.3.tgz", - "integrity": "sha512-eEMa6MKpHFzw38eKm56iNNi6GJ7lf6aLLio7Kr23sJPAECscgRtZvOBYybejWDQ2bM949Y++61PY+udzj5QMLA==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==" }, "is-alphanumeric": { "version": "1.0.0", @@ -13166,31 +13242,19 @@ } }, "mdast-util-to-hast": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-6.0.2.tgz", - "integrity": "sha512-GjcOimC9qHI0yNFAQdBesrZXzUkRdFleQlcoU8+TVNfDW6oLUazUx8MgUoTaUyCJzBOnE5AOgqhpURrSlf0QwQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-7.0.0.tgz", + "integrity": "sha512-vxnXKSZgvPG2grZM3kxaF052pxsLtq8TPAkiMkqYj1nFTOazYUPXt3LFYIEB6Ws/IX7Uyvljzk64kD6DwZl/wQ==", "requires": { "collapse-white-space": "^1.0.0", "detab": "^2.0.0", "mdast-util-definitions": "^1.2.0", "mdurl": "^1.0.1", - "trim": "0.0.1", "trim-lines": "^1.0.0", - "unist-builder": "^1.0.1", - "unist-util-generated": "^1.1.0", + "unist-builder": "^2.0.0", + "unist-util-generated": "^1.0.0", "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.0", - "xtend": "^4.0.1" - }, - "dependencies": { - "unist-util-visit": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", - "integrity": "sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==", - "requires": { - "unist-util-visit-parents": "^2.0.0" - } - } + "unist-util-visit": "^2.0.0" } }, "mdast-util-to-nlcst": { @@ -13205,9 +13269,9 @@ } }, "mdast-util-to-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.0.7.tgz", - "integrity": "sha512-P+gdtssCoHOX+eJUrrC30Sixqao86ZPlVjR5NEAoy0U79Pfxb1Y0Gntei0+GrnQD4T04X9xA8tcugp90cSmNow==" + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.0.8.tgz", + "integrity": "sha512-GBracya0dOzckEEizUBzfrkWRLCHMsppuU97LPUriY9kWnYyGFWTx4VDW+sUcj2LneBz/Tp1aYp3aUCibzjtWg==" }, "mdast-util-toc": { "version": "3.1.0", @@ -13310,9 +13374,9 @@ "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==" }, "mermaid": { - "version": "8.4.7", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.4.7.tgz", - "integrity": "sha512-mj4mefncBd8y921auvsXMN5MbVqzkrXyCUPz1AbVdQ+W6XKO27Oyqnor4ZO2hlqlosJc+Dl273V+SJBmX5PTNw==", + "version": "8.4.8", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.4.8.tgz", + "integrity": "sha512-sumTNBFwMX7oMQgogdr3NhgTeQOiwcEsm23rQ4KHGW7tpmvMwER1S+1gjCSSnqlmM/zw7Ga7oesYCYicKboRwQ==", "requires": { "@braintree/sanitize-url": "^3.1.0", "crypto-random-string": "^3.0.1", @@ -13709,9 +13773,9 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "nlcst-to-string": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-2.0.3.tgz", - "integrity": "sha512-OY2QhGdf6jpYfHqS4vJwqF7aIBZkaMjMUkcHcskMPitvXLuYNGdQvgVWI/5yKwkmIdmhft3ounSJv+Re2yydng==" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-2.0.4.tgz", + "integrity": "sha512-3x3jwTd6UPG7vi5k4GEzvxJ5rDA7hVUIRNHPblKuMVP9Z3xmlsd9cgLcpAMkc5uPOBna82EeshROFhsPkbnTZg==" }, "no-case": { "version": "2.3.2", @@ -16641,24 +16705,92 @@ } }, "remark-mdx": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-1.5.5.tgz", - "integrity": "sha512-w1XW9UzsQ6XAecV59dP8LJWn4tMftaXGwH5LEvUU5uIEJEJvHDE1jkKiPr3ow2IuhjuRfWs3b079Jtnk5qlUgQ==", - "requires": { - "@babel/core": "7.8.0", - "@babel/helper-plugin-utils": "7.8.0", - "@babel/plugin-proposal-object-rest-spread": "7.8.0", - "@babel/plugin-syntax-jsx": "7.8.0", - "@mdx-js/util": "^1.5.5", - "is-alphabetical": "1.0.3", + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-1.5.7.tgz", + "integrity": "sha512-f13ot+zaByDXYuOC4FWTpQCGP/rNbaxdhs2mLlW7ZBipm3JYR2ASFSL7RC3R7ytzm3n8v6hhcFxDKU+CwC2f4g==", + "requires": { + "@babel/core": "7.8.4", + "@babel/helper-plugin-utils": "7.8.3", + "@babel/plugin-proposal-object-rest-spread": "7.8.3", + "@babel/plugin-syntax-jsx": "7.8.3", + "@mdx-js/util": "^1.5.7", + "is-alphabetical": "1.0.4", "remark-parse": "7.0.2", "unified": "8.4.2" }, "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/core": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.4.tgz", + "integrity": "sha512-0LiLrB2PwrVI+a2/IEskBopDYSd8BCb3rOvH7D5tzoWd696TBEduBvuLVm4Nx6rltrLZqvI3MCalB2K2aVzQjA==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.4", + "@babel/helpers": "^7.8.4", + "@babel/parser": "^7.8.4", + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.8.4", + "@babel/types": "^7.8.3", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.0", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } + }, "@babel/helper-plugin-utils": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.0.tgz", - "integrity": "sha512-+hAlRGdf8fHQAyNnDBqTHQhwdLURLdrCROoWaEQYiQhk2sV9Rhs+GoFZZfMJExTq9HG8o2NX3uN2G90bFtmFdA==" + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + }, + "@babel/helpers": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.8.4.tgz", + "integrity": "sha512-VPbe7wcQ4chu4TDQjimHv/5tj73qz88o12EPkO2ValS2QiQS/1F2SsjyIGNnAD0vF/nZS6Cf9i+vW6HIlnaR8w==", + "requires": { + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.8.4", + "@babel/types": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/types": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } } } }, @@ -16734,6 +16866,14 @@ "inline-style-parser": "0.1.1" } }, + "unist-builder": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-1.0.4.tgz", + "integrity": "sha512-v6xbUPP7ILrT15fHGrNyHc1Xda8H3xVhP7/HAIotHOhVPjH5dCXA097C3Rry1Q2O+HbOLCao4hfPB+EYEjHgVg==", + "requires": { + "object-assign": "^4.1.0" + } + }, "unist-util-is": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-2.1.3.tgz", @@ -17179,20 +17319,79 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sanitize-html": { - "version": "1.21.1", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.21.1.tgz", - "integrity": "sha512-W6enXSVphVaVbmVbzVngBthR5f5sMmhq3EfPfBlzBzp2WnX8Rnk7NGpP7KmHUc0Y3MVk9tv/+CbpdHchX9ai7g==", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.22.0.tgz", + "integrity": "sha512-3RPo65mbTKpOAdAYWU496MSty1YbB3Y5bjwL5OclgaSSMtv65xvM7RW/EHRumzaZ1UddEJowCbSdK0xl5sAu0A==", "requires": { "chalk": "^2.4.1", - "htmlparser2": "^3.10.0", + "htmlparser2": "^4.1.0", "lodash.clonedeep": "^4.5.0", "lodash.escaperegexp": "^4.1.2", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.mergewith": "^4.6.1", - "postcss": "^7.0.5", - "srcset": "^1.0.0", + "postcss": "^7.0.27", + "srcset": "^2.0.1", "xtend": "^4.0.1" + }, + "dependencies": { + "domelementtype": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==" + }, + "domhandler": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz", + "integrity": "sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw==", + "requires": { + "domelementtype": "^2.0.1" + } + }, + "domutils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.0.0.tgz", + "integrity": "sha512-n5SelJ1axbO636c2yUtOGia/IcJtVtlhQbFiVDBZHKV5ReJO1ViX7sFEemtuyoAnBxk5meNSYgA8V4s0271efg==", + "requires": { + "dom-serializer": "^0.2.1", + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0" + } + }, + "htmlparser2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz", + "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==", + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0", + "domutils": "^2.0.0", + "entities": "^2.0.0" + } + }, + "postcss": { + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", + "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } } }, "sax": { @@ -18106,13 +18305,9 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "srcset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/srcset/-/srcset-1.0.0.tgz", - "integrity": "sha1-pWad4StC87HV6D7QPHEEb8SPQe8=", - "requires": { - "array-uniq": "^1.0.2", - "number-is-nan": "^1.0.0" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/srcset/-/srcset-2.0.1.tgz", + "integrity": "sha512-00kZI87TdRKwt+P8jj8UZxbfp7mK2ufxcIMWvhAOZNJTRROimpHeruWrGvCZneiuVDLqdyHefVp748ECTnyUBQ==" }, "sshpk": { "version": "1.16.1", @@ -19067,9 +19262,9 @@ "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==" }, "uglify-js": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.7.tgz", - "integrity": "sha512-FeSU+hi7ULYy6mn8PKio/tXsdSXN35lm4KgV2asx00kzrLU9Pi3oAslcJT70Jdj7PHX29gGUPOT6+lXGBbemhA==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.0.tgz", + "integrity": "sha512-ugNSTT8ierCsDHso2jkBHXYrU8Y5/fY2ZUprfrJUiD7YpuFvV4jODLFmb3h4btQjqr5Nh4TX4XtgDfCU1WdioQ==", "requires": { "commander": "~2.20.3", "source-map": "~0.6.1" @@ -19194,12 +19389,9 @@ } }, "unist-builder": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-1.0.4.tgz", - "integrity": "sha512-v6xbUPP7ILrT15fHGrNyHc1Xda8H3xVhP7/HAIotHOhVPjH5dCXA097C3Rry1Q2O+HbOLCao4hfPB+EYEjHgVg==", - "requires": { - "object-assign": "^4.1.0" - } + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-2.0.3.tgz", + "integrity": "sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw==" }, "unist-util-generated": { "version": "1.1.5", @@ -19292,9 +19484,9 @@ } }, "unist-util-visit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.1.tgz", - "integrity": "sha512-bEDa5S/O8WRDeI1mLaMoKuFFi89AjF+UAoMNxO+bbVdo06q+53Vhq4iiv1PenL6Rx1ZxIpXIzqZoc5HD2I1oMA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.2.tgz", + "integrity": "sha512-HoHNhGnKj6y+Sq+7ASo2zpVdfdRifhTgX2KTU3B/sO/TTlZchp7E3S4vjRzDJ7L60KmrCPsQkVK3lEF3cz36XQ==", "requires": { "@types/unist": "^2.0.0", "unist-util-is": "^4.0.0", diff --git a/docs/package.json b/docs/package.json index 6bc73259c96..3063dbf31a1 100644 --- a/docs/package.json +++ b/docs/package.json @@ -7,7 +7,7 @@ }, "dependencies": { "gatsby": "2.19.19", - "gatsby-theme-apollo-docs": "4.0.6", + "gatsby-theme-apollo-docs": "4.0.7", "react": "16.12.0", "react-dom": "16.12.0" } From 555af2162a3c73a10889c391adb255ec69c044b5 Mon Sep 17 00:00:00 2001 From: Stephen Barlow Date: Tue, 11 Feb 2020 13:54:46 -0800 Subject: [PATCH 02/10] WIP on improving core concepts of federation --- docs/source/federation/core-concepts.md | 63 ++++++++++++------------- 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/docs/source/federation/core-concepts.md b/docs/source/federation/core-concepts.md index 65cecfeb9a0..e7a544f2b2f 100644 --- a/docs/source/federation/core-concepts.md +++ b/docs/source/federation/core-concepts.md @@ -1,17 +1,15 @@ --- -title: Core concepts -description: How schema composition works +title: Working with entities +description: Reference types across services --- -Apollo Federation works through a declarative composition model where services expose their capabilities and together they can be formed into a single graph. This section describes the core concepts in the programming model. +In Apollo Federation, an **entity** is a type that you define canonically in _one_ implementing service and then reference and extend in _other_ implementing services. Entities are the core building block of a federated graph. -## Entities and keys +## Declaring an entity -An entity is a type that can be referenced by another service. Entities create connection points between services and form the basic building blocks of a federated graph. Entities have a primary key whose value uniquely identifies a specific instance of the type, similar to the function of a primary key in a SQL table. +In a GraphQL schema, you can designate an object type as an entity by adding a `@key` directive to its definition, like so: -Declaring an entity is done by adding a `@key` directive to the type definition. The directive takes one argument specifying the key: - -```graphql{1} +```graphql{1}:title=products type Product @key(fields: "upc") { upc: String! name: String! @@ -19,47 +17,53 @@ type Product @key(fields: "upc") { } ``` -In this example, the `@key` directive tells the Apollo query planner that a particular instance of `Product` can be fetched if you have its `upc`. Unlike Relay's [Node interface](https://facebook.github.io/relay/docs/en/graphql-server-specification.html#object-identification), keys can be any field (not just `ID`) and need not be globally unique. The ability to specify an entity's key makes it easier to build a data graph on top of existing APIs and services that already have a notion of a primary key, and encourages a more natural product-centric type definition. +The `@key` directive declares the entity's **primary key**, which consists of one or more of the type's `fields`. In the example above, the `Product` entity's primary key is its `upc` field. The Apollo query planner uses an entity's primary key to identify a given instance of the type. -> Apollo supports multiple keys for an entity and composite keys (combination of fields). See [advanced features](/federation/advanced-features/) for more information on these options. +> Apollo Federation supports [defining _multiple_ primary keys for an entity](./advanced-features/#multiple-primary-keys), along with [primary keys that consist of multiple fields](./advanced-features/#compound-and-nested-keys). -## Referencing external types +## Referencing an entity from another service -Once an entity is part of the graph, other services can begin to reference that type from their own types. Let's look at how the reviews service can join across services to return a `Product`: +After you define an entity in one implementing service, other implementing services can then reference that entity. If a `products` service defines the `Product` entity above, a `reviews` service can then add a field of type `Product` to its `Review` type, like so: -```graphql -# in the reviews service +```graphql:title=reviews type Review { product: Product } +# This is a "stub" of the Product entity (see below) extend type Product @key(fields: "upc") { upc: String! @external } ``` -In this example we have a `Review` type with a field called `product` that returns the `Product` type. Since `Product` is an entity that lives in another service, we define a *stub* of that type in this service with just enough information to enable composition. The syntax may look a bit strange at first, so let's unpack it: -- The `extend` keyword declares that `Product` is an entity defined elsewhere, in this case the product catalog service. -- The `@key` directive declares that we'll use a UPC to reference a particular product. This must match the referenced entity's own key as defined in the product catalog service. -- The definition of the `upc` field with an `@external` directive declares the type of the `upc` field (`String!`, in this case) that is implemented in another service. +Because the `Product` entity is defined in another service, the `reviews` service needs to define a **stub** of it to make its own schema valid. The stub includes just enough information for the service to know how to interact with a `Product`: + +* The `extend` keyword indicates that `Product` is an entity that is defined in another implementing service (in this case, `products`). +* The `@key` directive indicates that `Product` uses the `upc` field as its primary key. **This value must match the value of `@key` specified in the entity's originating service.** +* The `upc` field must be included in the stub because it is part of the entity's primary key. It also must be annotated with the `@external` directive to indicate that the field originates from another service. + +This explicit syntax has several benefits: +* It is standard GraphQL grammar. +* It enables you to run the `reviews` service standalone with a valid schema, including a `Product` type with a single `upc` field. +* It provides strong typing information that lets you catch mistakes at schema composition time. -This explicit syntax has several benefits. It is standard GraphQL grammar. It allows us to run the reviews service standalone with a valid schema, including a `Product` type with a single `upc` field. And it provides strong typing information that lets us catch mistakes at schema composition time. +## Resolving an entity from another service -With the type definitions in place, we can write a resolver for `Review.product`. Instead of returning a complete `Product` object (we can't; this service doesn't know much about products), the resolver just returns a reference to the external type. +In our example, the `reviews` service needs to define its own resolver for the `Product` entity. The `reviews` service doesn't know much about `Product`s, but fortunately, it doesn't need to. All it needs to do is return enough information to uniquely identify a given `Product`, like so: ```js { Review: { product(review) { - return { __typename: "Product", upc: review.product_upc }; + return { __typename: "Product", upc: review.upc }; } } } ``` -The `{ __typename: "Product", upc: review.product_upc }` object is a *representation* of a `Product` entity. Representations are how services reference each others' types. They contain an explicit typename definition and a value for the key. +This return value is a **representation** of a `Product` entity. Implementing services use representations to reference entities from other services. All a representation requires is an explicit `__typename` definition and values for the entity's primary key fields. -The gateway will use the representation as an input to the service that owns the referenced entity. So to allow the gateway to enter the graph in this manner and resume execution of the query, the last thing we need is a *reference resolver* back in the product catalog service. We only write this once per entity. +Your federated gateway provides this representation to the entity's originating service to fetch the full object. For this to work, the originating service (in this case, `products`) must define a **reference resolver** for the `Product` entity: ```js{3-5} { @@ -71,9 +75,9 @@ The gateway will use the representation as an input to the service that owns the } ``` -> Reference resolvers are a special addition to Apollo Server that allow individual types to be resolved by a reference from another service. They are called when a query references an `entity` across service boundaries. To learn more about `__resolveReference`, see the [API docs](/api/apollo-federation/). +> Reference resolvers are a special addition to Apollo Server that enable an entity to be resolved by another service. They are called whenever a query references an `entity` across service boundaries. To learn more about `__resolveReference`, see the [API docs](/api/apollo-federation/). -What is nice about this model is that we end up with a schema that represents a true subset of the overall graph, as opposed to a mangled schema with foreign key fields like `productID`. Ultimately, this means clients can write queries like this without having to ask for special fields or make additional requests to other services. +With this model, each implementing service ends up with a schema that represents a true subset of the complete data graph, as opposed to complex individual schemas that define foreign key fields like `productID`. This enables clients to transparently execute a query like the following, which hits both the `products` and `reviews` services: ```graphql { @@ -86,12 +90,7 @@ What is nice about this model is that we end up with a schema that represents a } ``` -So to review: to reference an external entity from a resolver, we -1. Define a stub type for the entity we want to reference. The key as declared in the stub must match the entity's own declaration. -2. From the referencing resolver, return a representation. -3. In the referenced service, implement a reference resolver for the entity. - -## Extending external types +## Extending an entity from another service Returning a reference to an author represents just one side of a relationship. A true data graph should expose the ability to navigate relationships in both directions. You'll want to be able to go from a product to its reviews, for example. While these fields are exposed on `Product` for the client to query, they can't be part of the accounts service because they are a concern of the reviews service. @@ -233,5 +232,3 @@ extend type Product @key(fields: "sku"){ sku: ID! @external } ``` - - From 1f3c997f38522624050efa64f8e3c174a7a8e244 Mon Sep 17 00:00:00 2001 From: Stephen Barlow Date: Tue, 11 Feb 2020 16:03:18 -0800 Subject: [PATCH 03/10] Edit core concepts to be about entities, break out value types --- docs/source/federation/core-concepts.md | 133 ++++++++---------------- docs/source/federation/value-types.md | 71 +++++++++++++ 2 files changed, 114 insertions(+), 90 deletions(-) create mode 100644 docs/source/federation/value-types.md diff --git a/docs/source/federation/core-concepts.md b/docs/source/federation/core-concepts.md index e7a544f2b2f..907bbe21f77 100644 --- a/docs/source/federation/core-concepts.md +++ b/docs/source/federation/core-concepts.md @@ -1,13 +1,13 @@ --- title: Working with entities -description: Reference types across services +description: Reference and extend another service's types --- -In Apollo Federation, an **entity** is a type that you define canonically in _one_ implementing service and then reference and extend in _other_ implementing services. Entities are the core building block of a federated graph. +In Apollo Federation, an **entity** is a type that you define canonically in _one_ implementing service and can then reference and extend in _other_ implementing services. Entities are the core building block of a federated graph. -## Declaring an entity +## Declaring -In a GraphQL schema, you can designate an object type as an entity by adding a `@key` directive to its definition, like so: +In a GraphQL schema, you can designate any object type as an entity by adding a `@key` directive to its definition, like so: ```graphql{1}:title=products type Product @key(fields: "upc") { @@ -17,11 +17,11 @@ type Product @key(fields: "upc") { } ``` -The `@key` directive declares the entity's **primary key**, which consists of one or more of the type's `fields`. In the example above, the `Product` entity's primary key is its `upc` field. The Apollo query planner uses an entity's primary key to identify a given instance of the type. +The `@key` directive declares the entity's **primary key**, which consists of one or more of the type's `fields`. In the example above, the `Product` entity's primary key is its `upc` field. Apollo Gateway's query planner uses an entity's primary key to identify a given instance of the type. > Apollo Federation supports [defining _multiple_ primary keys for an entity](./advanced-features/#multiple-primary-keys), along with [primary keys that consist of multiple fields](./advanced-features/#compound-and-nested-keys). -## Referencing an entity from another service +## Referencing After you define an entity in one implementing service, other implementing services can then reference that entity. If a `products` service defines the `Product` entity above, a `reviews` service can then add a field of type `Product` to its `Review` type, like so: @@ -38,16 +38,16 @@ extend type Product @key(fields: "upc") { Because the `Product` entity is defined in another service, the `reviews` service needs to define a **stub** of it to make its own schema valid. The stub includes just enough information for the service to know how to interact with a `Product`: -* The `extend` keyword indicates that `Product` is an entity that is defined in another implementing service (in this case, `products`). +* The `extend` keyword indicates that `Product` is an entity that is defined in another implementing service (in this case, the `products` service). * The `@key` directive indicates that `Product` uses the `upc` field as its primary key. **This value must match the value of `@key` specified in the entity's originating service.** -* The `upc` field must be included in the stub because it is part of the entity's primary key. It also must be annotated with the `@external` directive to indicate that the field originates from another service. +* The `upc` field must be included in the stub because it is part of the entity's primary key. It also must be annotated with the `@external` directive to indicate that the field originates in another service. This explicit syntax has several benefits: * It is standard GraphQL grammar. * It enables you to run the `reviews` service standalone with a valid schema, including a `Product` type with a single `upc` field. * It provides strong typing information that lets you catch mistakes at schema composition time. -## Resolving an entity from another service +## Resolving In our example, the `reviews` service needs to define its own resolver for the `Product` entity. The `reviews` service doesn't know much about `Product`s, but fortunately, it doesn't need to. All it needs to do is return enough information to uniquely identify a given `Product`, like so: @@ -61,9 +61,9 @@ In our example, the `reviews` service needs to define its own resolver for the ` } ``` -This return value is a **representation** of a `Product` entity. Implementing services use representations to reference entities from other services. All a representation requires is an explicit `__typename` definition and values for the entity's primary key fields. +This return value is a **representation** of a `Product` entity. Services use representations to reference entities from other services. A representation requires only an explicit `__typename` definition and values for the entity's primary key fields. -Your federated gateway provides this representation to the entity's originating service to fetch the full object. For this to work, the originating service (in this case, `products`) must define a **reference resolver** for the `Product` entity: +Apollo Gateway provides this representation to the entity's originating service to fetch the full object. For this to work, the originating service (in this case, `products`) must define a **reference resolver** for the `Product` entity: ```js{3-5} { @@ -75,9 +75,9 @@ Your federated gateway provides this representation to the entity's originating } ``` -> Reference resolvers are a special addition to Apollo Server that enable an entity to be resolved by another service. They are called whenever a query references an `entity` across service boundaries. To learn more about `__resolveReference`, see the [API docs](/api/apollo-federation/). +> Reference resolvers are a special addition to Apollo Server that enable entities to be referenced by other services. They are called whenever a query references an `entity` across service boundaries. To learn more about `__resolveReference`, see the [API docs](/api/apollo-federation/). -With this model, each implementing service ends up with a schema that represents a true subset of the complete data graph, as opposed to complex individual schemas that define foreign key fields like `productID`. This enables clients to transparently execute a query like the following, which hits both the `products` and `reviews` services: +With this model, each implementing service's schema represents a true subset of the complete data graph. This prevents the need for defining foreign-key fields in individual schemas, and enables clients to transparently execute a query like the following, which hits both the `products` and `reviews` services: ```graphql { @@ -90,26 +90,30 @@ With this model, each implementing service ends up with a schema that represents } ``` -## Extending an entity from another service +## Extending -Returning a reference to an author represents just one side of a relationship. A true data graph should expose the ability to navigate relationships in both directions. You'll want to be able to go from a product to its reviews, for example. While these fields are exposed on `Product` for the client to query, they can't be part of the accounts service because they are a concern of the reviews service. +An implementing service can also add fields to an entity that's defined in another service. This is called **extending** the entity. -The `extend type` mechanism is all we need for this use case. While the `Product` type belongs to the product catalog service, other services can define extension fields on this type using the `extend type` syntax. +When a service extends an entity, the entity's _originating_ service is not aware of the added fields. Only the _extending_ service (along with Apollo Gateway) knows about these fields. -The query planner will make sure the fields required by a resolver on an extension field are requested from the service hosting the type even if the user didn't request them directly. Every resolver that is added to a type from another service will receive the fields requested in the `@key` directive on the type extension. +> It is invalid for more than one service to extend the same entity with the same field name. Each individual field of an entity must originate in exactly one service. -For example, if we wanted to add a reviews field to the `Product` type: +### Example #1 -```graphql{3} +Let's say we want to add a `reviews` field to the `Product` entity. This field will hold a list of reviews for the product. The `Product` entity originates in the `products` service, but it makes much more sense for the `reviews` service to resolve this particular field. + +To handle this case, we can extend the `Product` entity in the `reviews` service like so: + +```graphql{3}:title=reviews extend type Product @key(fields: "upc") { upc: String! @external reviews: [Review] } ``` -Since the reviews service already had a concept of the `Product` type from returning it, adding additional fields to the overall type can be done just like it was a normal type. +As you can see, this definition is nearly identical to the stub we defined for the `Product` type in [Referencing](#referencing). All we've added is the `reviews` field. We _don't_ add an `@external` directive to the field, because it _does_ originate in the `reviews` service. -The generated query plan will fetch the `upc` field for each `Product` from the product catalog service and pass those to the reviews service, where you can then access these fields on the object passed into your `reviews` resolver: +Because the `reviews` service adds the `reviews` field, it is also responsible for _resolving_ the field. Apollo Gateway is automatically aware of this responsibility. The generated query plan will fetch the `upc` field for each `Product` from the `products` service and pass those to the `reviews` service, where you can then access these fields on the object passed into your `reviews` resolver: ```js { @@ -121,9 +125,11 @@ The generated query plan will fetch the `upc` field for each `Product` from the } ``` -Type extensions aren't just useful for relationships. You can also use them to extend types with additional scalar or other value fields. Here, we want to be able to query for the `inStock` status of a product. That information lives in an inventory service, so we'll add the type extension there: +### Example #2 + +Let's say we want to be able to query for the `inStock` status of a product. That information lives in an `inventory` service, so we'll add the type extension there: -```graphql{3} +```graphql{3}:title=inventory extend type Product @key(fields: "upc") { upc: ID! @external inStock: Boolean @@ -140,7 +146,7 @@ extend type Product @key(fields: "upc") { } ``` -Similar to the `reviews` relationship example above, the gateway will fetch the required `upc` field from the product catalog service and pass it to the inventory service, even if the query didn't ask for the `upc`: +Similar to the `reviews` relationship example above, Apollo Gateway fetches the required `upc` field from the `products` service and passes it to the `inventory` service, even if the query didn't ask for the `upc`: ```graphql query { @@ -150,85 +156,32 @@ query { } ``` -## Root queries and mutations +## The `Query` and `Mutation` entities -Since `Query` and `Mutation` are regular types in GraphQL, we use the same `extend type` pattern to define root queries. This gives us a mechanism for how each service can define root queries for the composed graph. +In Apollo Federation, the `Query` and `Mutation` base types are _automatically_ entities that originate in Apollo Gateway itself. Consequently, _all_ of your implementing services should [extend](#extending) these types to add the operations they support. -To implement a root query, such as `topProducts`, we simply extend the `Query` type: +For example, the `products` service might extend the root `Query` type to add a `topProducts` query, like so: -```graphql +```graphql:title=products extend type Query { topProducts(first: Int = 5): [Product] } ``` -There is no need to explicitly define `Query` or `Mutation` base types anywhere; Apollo automatically handles this for you. - -## Value Types - -A natural overlap among identical types between services is not uncommon. Rather than having a single service "own" those types, all services that use them are expected to share ownership. This form of type "duplication" across services is supported for Scalars, Objects, Interfaces, Enums, Unions, and Inputs. The rule of thumb for any of these value types is that the types **must be identical** in name and contents. +## Changing a field's originating service -### Objects, Interfaces, and Inputs -For types with field definitions, all fields _and their types_ must be identical. +As your federated graph grows, you might decide that you want a particular field of an entity to originate in a different service. -### Scalars -For Scalar values, it's important that services **share the same serialization and parsing logic**, since there is no way to validate that logic from the schema level by federation tooling. +For example, let's say the `products` service defines a `Product` entity that includes an `inStock` boolean field. Then, you add an `inventory` service to your federated graph. It now makes sense for the `inStock` field to originate in the `inventory` service instead. -### Enums -For Enum types, all values must match across services. **Even if a service doesn't use all values in an Enum, they still must be defined in the schema**. Failure to include all enum values in all services that use the Enum will result in a validation error when building the federated schema. +Apollo Gateway helps you perform this migration much like you perform a database migration, with the following steps: -### Unions -Union types must share the same types in the union, even if not all types are used by a service. +1. In the `inventory` service, [extend](#extending) the `Product` entity to add the `inStock` field. **Note that this is technically a federation error**, because this field is already defined in the `products` service. -In the following example, the Product and User services both use the same `ProductCategory` enum, `Date` scalar, `Error` type, and `ProductOrError` union. +2. In the `inventory` service, add a resolver for the `inStock` field. This service should resolve the field with the exact same outcome as the resolver in the `products` service. -```graphql -# Product Service -scalar Date - -union ProductOrError = Product | Error - -type Error { - code: Int! - message: String! -} +3. Push the updated `inventory` service to your environment. **Again, this is a federation error**. When Apollo Gateway detects a duplicate field like this, it ignores the _newer_ declaration (i.e., the one in the `inventory` service) and continues to resolve the field in the `products` service. -type Product @key(fields: "sku"){ - sku: ID! - category: ProductCategory - dateCreated: Date -} +4. In the `products` service, remove the `inStock` field and its resolver. -enum ProductCategory { - FURNITURE - BOOK - DIGITAL_DOWNLOAD -} - -# User Service -scalar Date - -union ProductOrError = Product | Error - -type Error { - code: Int! - message: String! -} - -type User @key(fields: "id"){ - id: ID! - dateCreated: Date - favoriteCategory: ProductCategory - favoriteProducts: [Product!] -} - -enum ProductCategory { - FURNITURE - BOOK - DIGITAL_DOWNLOAD -} - -extend type Product @key(fields: "sku"){ - sku: ID! @external -} -``` +5. Push the updated `products` service to your environment. This removes the invalid duplicate, and Apollo Gateway will begin resolving the `inStock` field in the `inventory` service. diff --git a/docs/source/federation/value-types.md b/docs/source/federation/value-types.md new file mode 100644 index 00000000000..78c6fd20f3d --- /dev/null +++ b/docs/source/federation/value-types.md @@ -0,0 +1,71 @@ +--- +title: Value types +description: Define the exact same type in multiple services +--- + +A natural overlap among identical types between services is not uncommon. Rather than having a single service "own" those types, all services that use them are expected to share ownership. This form of type "duplication" across services is supported for Scalars, Objects, Interfaces, Enums, Unions, and Inputs. The rule of thumb for any of these value types is that the types **must be identical** in name and contents. + +## Objects, Interfaces, and Inputs +For types with field definitions, all fields _and their types_ must be identical. + +## Scalars +For Scalar values, it's important that services **share the same serialization and parsing logic**, since there is no way to validate that logic from the schema level by federation tooling. + +## Enums +For Enum types, all values must match across services. **Even if a service doesn't use all values in an Enum, they still must be defined in the schema**. Failure to include all enum values in all services that use the Enum will result in a validation error when building the federated schema. + +## Unions +Union types must share the same types in the union, even if not all types are used by a service. + +In the following example, the Product and User services both use the same `ProductCategory` enum, `Date` scalar, `Error` type, and `ProductOrError` union. + +```graphql +# Product Service +scalar Date + +union ProductOrError = Product | Error + +type Error { + code: Int! + message: String! +} + +type Product @key(fields: "sku"){ + sku: ID! + category: ProductCategory + dateCreated: Date +} + +enum ProductCategory { + FURNITURE + BOOK + DIGITAL_DOWNLOAD +} + +# User Service +scalar Date + +union ProductOrError = Product | Error + +type Error { + code: Int! + message: String! +} + +type User @key(fields: "id"){ + id: ID! + dateCreated: Date + favoriteCategory: ProductCategory + favoriteProducts: [Product!] +} + +enum ProductCategory { + FURNITURE + BOOK + DIGITAL_DOWNLOAD +} + +extend type Product @key(fields: "sku"){ + sku: ID! @external +} +``` From eaaab554b381c2a6a9783431f5092ecdb6f20b81 Mon Sep 17 00:00:00 2001 From: Stephen Barlow Date: Tue, 11 Feb 2020 16:04:41 -0800 Subject: [PATCH 04/10] Add Value Types article to left nav --- docs/gatsby-config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/gatsby-config.js b/docs/gatsby-config.js index 6ccbf25bc63..a28743c5553 100644 --- a/docs/gatsby-config.js +++ b/docs/gatsby-config.js @@ -39,6 +39,7 @@ module.exports = { 'federation/introduction', 'federation/implementing', 'federation/core-concepts', + 'federation/value-types', 'federation/advanced-features', 'federation/errors', 'federation/metrics', From 821f76bf8c831935905c57d1b949b4252c83ce5f Mon Sep 17 00:00:00 2001 From: Stephen Barlow Date: Tue, 11 Feb 2020 16:08:06 -0800 Subject: [PATCH 05/10] Fix a pesky broken link --- docs/source/federation/errors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/federation/errors.md b/docs/source/federation/errors.md index b544ea3ff5b..46eea0c79d0 100644 --- a/docs/source/federation/errors.md +++ b/docs/source/federation/errors.md @@ -89,4 +89,4 @@ If Apollo Gateway encounters an error, composition fails. This document lists co | Code | Description | |---|---| -| Unique type names | Type definitions cannot be duplicated across services, with the exception of enums, scalars, and [value types](/federation/core-concepts/#value-types). This is a modified version of the `graphql-js` validation with exclusions for enums and scalars, because those are required to be duplicated across services. | +| Unique type names | Type definitions cannot be duplicated across services, with the exception of enums, scalars, and [value types](/federation/value-types/). This is a modified version of the `graphql-js` validation with exclusions for enums and scalars, because those are required to be duplicated across services. | From 646ff84f31f11f4763b144925435e8f417abcd6c Mon Sep 17 00:00:00 2001 From: Stephen Barlow Date: Fri, 14 Feb 2020 15:31:12 -0800 Subject: [PATCH 06/10] Almost done moving advanced features into other docs --- docs/source/federation/advanced-features.md | 92 --------------------- docs/source/federation/core-concepts.md | 72 +++++++++++++++- 2 files changed, 68 insertions(+), 96 deletions(-) diff --git a/docs/source/federation/advanced-features.md b/docs/source/federation/advanced-features.md index 08f81b6903a..432c407f50c 100644 --- a/docs/source/federation/advanced-features.md +++ b/docs/source/federation/advanced-features.md @@ -3,59 +3,6 @@ title: Advanced features description: Patterns for complex graphs --- -Federation supports several advanced features that make it easier to integrate with legacy APIs or build complex schemas. - -## Multiple primary keys - -In some cases there may be multiple ways of referring to an entity, such as when we refer to a user either by ID or by email. This pattern is especially common when a type spans services: your review system may refer to a product by UPC, while your inventory system stores SKUs. - -Therefore, the programming model allows types to define multiple keys, which indicates they can be looked up in one of several ways: - -```graphql -type Product @key(fields: "upc") @key(fields: "sku") { - upc: String! - sku: String! - price: String -} -``` - -> Note the difference from `@key(fields: "upc sku")`, a composite key, which would mean that only the combination of UPC and SKU is unique. See below. - -Multiple keys are only allowed on the base type, not on type extensions. Type extensions are used to define external types, so a `@key` directive there is meant to specify which key of the base type will be used as a foreign key by the service that contains the type extension. For example, our reviews service could use `upc`: - -```graphql -extend type Product @key(fields: "upc") { - upc: String! @external - reviews: [Review] -} -``` - -While the inventory service uses `sku`: - -```graphql -extend type Product @key(fields: "sku") { - sku: ID! @external - inStock: Boolean -} -``` - -## Compound and nested keys - -Keys may be complex and include nested fields, as when a user's ID is only unique within its organization: - -```graphql -type User @key(fields: "id organization { id }") { - id: ID! - organization: Organization! -} - -type Organization { - id: ID! -} -``` - -> Note that although the fields argument is parsed as a selection set, some restrictions apply to make the result suitable as a key. For example, fields shouldn't return unions or interfaces. - ## Computed fields In many cases, what you need to resolve an extension field is a foreign key, which you specify through the `@key` directive on the type extension. With the `@requires` directive however, you can require any additional combination of fields (including subfields) from the base type that you may need in your resolver. For example, you may need access to a product's size and weight to calculate a shipping estimate: @@ -82,42 +29,3 @@ If a client requests `shippingEstimate`, the query planner will now request `siz ``` > Note that you can only require fields that live on the original type definition, not on type extensions defined in other services. - -## Using denormalized data - -In some cases, a service will be able to provide additional fields, even if these are not part of a key. For example, our review system may store the user's name in addition to the id, so we don't have to perform a separate fetch to the accounts service to get it. We can indicate which additional fields can be queried on the referenced type using the `@provides` directive: - -```graphql{2,7} -type Review { - author: User @provides(fields: "username") -} - -extend type User @key(fields: "id") { - id: ID! @external - username: String @external -} -``` - -The `@provides` directive acts as a hint to the gateway - -```js{4} -{ - Review: { - author(review) { - return { id: review.authorID, username: review.authorUsername }; - } - } -} -``` - -This knowledge can be used by the gateway to generate a more efficient query plan and avoids a fetch to a separate service because a field is already provided. In this case, we can return the author's name as part of the fetch to the reviews service: - -```graphql -query { - topReviews { - author { - username - } - } -} -``` diff --git a/docs/source/federation/core-concepts.md b/docs/source/federation/core-concepts.md index 907bbe21f77..83e3ce7848d 100644 --- a/docs/source/federation/core-concepts.md +++ b/docs/source/federation/core-concepts.md @@ -5,6 +5,8 @@ description: Reference and extend another service's types In Apollo Federation, an **entity** is a type that you define canonically in _one_ implementing service and can then reference and extend in _other_ implementing services. Entities are the core building block of a federated graph. +>In a federated graph, every object that is _not_ an entity is a [value type](./value-types/). + ## Declaring In a GraphQL schema, you can designate any object type as an entity by adding a `@key` directive to its definition, like so: @@ -19,7 +21,42 @@ type Product @key(fields: "upc") { The `@key` directive declares the entity's **primary key**, which consists of one or more of the type's `fields`. In the example above, the `Product` entity's primary key is its `upc` field. Apollo Gateway's query planner uses an entity's primary key to identify a given instance of the type. -> Apollo Federation supports [defining _multiple_ primary keys for an entity](./advanced-features/#multiple-primary-keys), along with [primary keys that consist of multiple fields](./advanced-features/#compound-and-nested-keys). +> An entity's `@key` cannot include fields that hold unions or interfaces. + +### Defining multiple primary keys + +If an entity can be uniquely identified by more than one combination of fields, you can define more than one primary key for that entity. + +In the following example, a `Product` entity can be uniquely identified by either its `upc` _or_ its `sku`: + +```graphql +type Product @key(fields: "upc") @key(fields: "sku") { + upc: String! + sku: String! + price: String +} +``` + +This pattern is helpful when different services interact with different fields of an entity. For example, a `reviews` service might refer to products by their UPC, whereas an `inventory` service might use SKUs. + +> You cannot list multiple primary keys when you [extend an entity](#extending), only when you declare the entity in its originating service. + +### Defining a compound primary key + +A single primary key can consist of multiple fields, and even nested fields. + +The following example shows a primary key that consists of both a user's `id` _and_ the `id` of that user's associated organization: + +```graphql +type User @key(fields: "id organization { id }") { + id: ID! + organization: Organization! +} + +type Organization { + id: ID! +} +``` ## Referencing @@ -39,8 +76,8 @@ extend type Product @key(fields: "upc") { Because the `Product` entity is defined in another service, the `reviews` service needs to define a **stub** of it to make its own schema valid. The stub includes just enough information for the service to know how to interact with a `Product`: * The `extend` keyword indicates that `Product` is an entity that is defined in another implementing service (in this case, the `products` service). -* The `@key` directive indicates that `Product` uses the `upc` field as its primary key. **This value must match the value of `@key` specified in the entity's originating service.** -* The `upc` field must be included in the stub because it is part of the entity's primary key. It also must be annotated with the `@external` directive to indicate that the field originates in another service. +* The `@key` directive indicates that `Product` uses the `upc` field as its primary key. **This value must match the value of a `@key` specified in the entity's originating service.** +* The `upc` field must be included in the stub because it is part of the specified `@key`. It also must be annotated with the `@external` directive to indicate that the field originates in another service. This explicit syntax has several benefits: * It is standard GraphQL grammar. @@ -168,7 +205,7 @@ extend type Query { } ``` -## Changing a field's originating service +## Migrating a field As your federated graph grows, you might decide that you want a particular field of an entity to originate in a different service. @@ -185,3 +222,30 @@ Apollo Gateway helps you perform this migration much like you perform a database 4. In the `products` service, remove the `inStock` field and its resolver. 5. Push the updated `products` service to your environment. This removes the invalid duplicate, and Apollo Gateway will begin resolving the `inStock` field in the `inventory` service. + +## Resolving another service's field (advanced) + +Sometimes, multiple implementing services are capable of resolving a particular field for an entity, because all of those services have access to a particular data store. For example, an `inventory` service and a `products` service might both have access to the database that stores all product-related data. + +When you [extend an entity](#extending) in this case, you can specify that the extending service `@provides` the field, like so: + +```graphql{2,8-9}:title=inventory +type InStockCont { + product: Product! @provides(fields: "name price") + quantity: Integer! +} + +extend type Product @key(fields: "sku") { + sku: String! @external + name: String @external + price: Integer @external +} +``` + +**This is a completely optional optimization.** When Apollo Gateway plans a query's execution, it looks at which fields are available from each implementing service. It then attempts to optimize performance by executing the query across the fewest services needed to access all required fields. + +Keep the following in mind when using the `@provides` directive: + +* Each service that `@provides` a field must also define a resolver for that field. **That resolver's behavior must match the behavior of the resolver in the field's originating service.** +* When an entity's field can be fetched from multiple services, there is no guarantee as to _which_ service will resolve that field for a particular query. +* If a service `@provides` a field, it must still list that field as `@external`, because the field originates in another service. From b40517665d59ffba34e54727f963baa1491b8623 Mon Sep 17 00:00:00 2001 From: Stephen Barlow Date: Fri, 14 Feb 2020 16:07:37 -0800 Subject: [PATCH 07/10] Get the remainder of advanced features into the entities article --- docs/source/federation/core-concepts.md | 43 +++++++++++++++++++++---- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/docs/source/federation/core-concepts.md b/docs/source/federation/core-concepts.md index 83e3ce7848d..08a343bcdfa 100644 --- a/docs/source/federation/core-concepts.md +++ b/docs/source/federation/core-concepts.md @@ -29,7 +29,7 @@ If an entity can be uniquely identified by more than one combination of fields, In the following example, a `Product` entity can be uniquely identified by either its `upc` _or_ its `sku`: -```graphql +```graphql{1}:title=products type Product @key(fields: "upc") @key(fields: "sku") { upc: String! sku: String! @@ -47,7 +47,7 @@ A single primary key can consist of multiple fields, and even nested fields. The following example shows a primary key that consists of both a user's `id` _and_ the `id` of that user's associated organization: -```graphql +```graphql{1}:title=directory type User @key(fields: "id organization { id }") { id: ID! organization: Organization! @@ -129,7 +129,7 @@ With this model, each implementing service's schema represents a true subset of ## Extending -An implementing service can also add fields to an entity that's defined in another service. This is called **extending** the entity. +An implementing service can add fields to an entity that's defined in another service. This is called **extending** the entity. When a service extends an entity, the entity's _originating_ service is not aware of the added fields. Only the _extending_ service (along with Apollo Gateway) knows about these fields. @@ -139,7 +139,7 @@ When a service extends an entity, the entity's _originating_ service is not awar Let's say we want to add a `reviews` field to the `Product` entity. This field will hold a list of reviews for the product. The `Product` entity originates in the `products` service, but it makes much more sense for the `reviews` service to resolve this particular field. -To handle this case, we can extend the `Product` entity in the `reviews` service like so: +To handle this case, we can extend the `Product` entity in the `reviews` service, like so: ```graphql{3}:title=reviews extend type Product @key(fields: "upc") { @@ -148,9 +148,9 @@ extend type Product @key(fields: "upc") { } ``` -As you can see, this definition is nearly identical to the stub we defined for the `Product` type in [Referencing](#referencing). All we've added is the `reviews` field. We _don't_ add an `@external` directive to the field, because it _does_ originate in the `reviews` service. +This definition is nearly identical to the stub we defined for the `Product` type in [Referencing](#referencing). All we've added is the `reviews` field. We _don't_ include an `@external` directive, because this field _does_ originate in the `reviews` service. -Because the `reviews` service adds the `reviews` field, it is also responsible for _resolving_ the field. Apollo Gateway is automatically aware of this responsibility. The generated query plan will fetch the `upc` field for each `Product` from the `products` service and pass those to the `reviews` service, where you can then access these fields on the object passed into your `reviews` resolver: +Whenever a service extends an entity with a new field, it is also responsible for _resolving_ the field. Apollo Gateway is automatically aware of this responsibility. In our example, the generated query plan will fetch the `upc` field for each `Product` from the `products` service and pass those to the `reviews` service, where you can then access these fields on the object passed into your `reviews` resolver: ```js { @@ -205,7 +205,7 @@ extend type Query { } ``` -## Migrating a field +## Migrating a field to another service (advanced) As your federated graph grows, you might decide that you want a particular field of an entity to originate in a different service. @@ -223,6 +223,35 @@ Apollo Gateway helps you perform this migration much like you perform a database 5. Push the updated `products` service to your environment. This removes the invalid duplicate, and Apollo Gateway will begin resolving the `inStock` field in the `inventory` service. +## Extending an entity with computed fields (advanced) + +When you [extend an entity](#extending), you can define fields that depend on fields in the entity's originating service. For example, a `shipping` service might extend the `Product` entity with a `shippingEstimate` field, which is calculated based on the product's `size` and `weight`: + +```graphql{5}:title=shipping +extend type Product @key(fields: "sku") { + sku: ID! @external + size: Int @external + weight: Int @external + shippingEstimate: String @requires(fields: "size weight") +} +``` + +As shown, you use the `@requires` directive to indicate which fields (and subfields) from the entity's originating service are required. + +>You **cannot** require fields that are defined in a service besides the entity's originating service. + +In the above example, if a client requests a product's `shippingEstimate`, Apollo Gateway will first obtain the product's `size` and `weight` from the `products` service, then pass those values to the `shipping` service. This enables you to access those values directly from your resolver: + +```js{4} +{ + Product: { + shippingEstimate(product) { + return computeShippingEstimate(product.sku, product.size, product.weight); + } + } +} +``` + ## Resolving another service's field (advanced) Sometimes, multiple implementing services are capable of resolving a particular field for an entity, because all of those services have access to a particular data store. For example, an `inventory` service and a `products` service might both have access to the database that stores all product-related data. From 359338c1795101bed58c8ca291d62b60eb0d412a Mon Sep 17 00:00:00 2001 From: Stephen Barlow Date: Fri, 14 Feb 2020 16:09:26 -0800 Subject: [PATCH 08/10] Delete Advanced Features article and set up redirect --- docs/gatsby-config.js | 1 - docs/source/federation/advanced-features.md | 31 --------------------- docs/static/_redirects | 1 + 3 files changed, 1 insertion(+), 32 deletions(-) delete mode 100644 docs/source/federation/advanced-features.md diff --git a/docs/gatsby-config.js b/docs/gatsby-config.js index a28743c5553..00082a78086 100644 --- a/docs/gatsby-config.js +++ b/docs/gatsby-config.js @@ -40,7 +40,6 @@ module.exports = { 'federation/implementing', 'federation/core-concepts', 'federation/value-types', - 'federation/advanced-features', 'federation/errors', 'federation/metrics', 'federation/migrating-from-stitching', diff --git a/docs/source/federation/advanced-features.md b/docs/source/federation/advanced-features.md deleted file mode 100644 index 432c407f50c..00000000000 --- a/docs/source/federation/advanced-features.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -title: Advanced features -description: Patterns for complex graphs ---- - -## Computed fields - -In many cases, what you need to resolve an extension field is a foreign key, which you specify through the `@key` directive on the type extension. With the `@requires` directive however, you can require any additional combination of fields (including subfields) from the base type that you may need in your resolver. For example, you may need access to a product's size and weight to calculate a shipping estimate: - -```graphql{5} -extend type Product @key(fields: "sku") { - sku: ID! @external - size: Int @external - weight: Int @external - shippingEstimate: String @requires(fields: "size weight") -} -``` - -If a client requests `shippingEstimate`, the query planner will now request `size` and `weight` from the base `Product` type, and pass it through to your service, so you can access them directly from your resolver in the exact same way you would if `Product` was contained within a single service: - -```js{4} -{ - Product: { - shippingEstimate(product) { - return computeShippingEstimate(product.sku, product.size, product.weight); - } - } -} -``` - -> Note that you can only require fields that live on the original type definition, not on type extensions defined in other services. diff --git a/docs/static/_redirects b/docs/static/_redirects index 7f7d6f1ed51..d5afb95ecc8 100644 --- a/docs/static/_redirects +++ b/docs/static/_redirects @@ -1,5 +1,6 @@ / /docs/apollo-server/ +/docs/apollo-server/federation/advanced-features/ /docs/apollo-server/federation/core-concepts/ /docs/apollo-server/federation/concerns/ /docs/apollo-server/federation/introduction/#separation-of-concerns # The test-utils lived at its current home for about 5 days. From 242cecdc3c789806e28d17e05d422f06b0b1eb40 Mon Sep 17 00:00:00 2001 From: Stephen Barlow Date: Mon, 24 Feb 2020 13:45:48 -0800 Subject: [PATCH 09/10] Incorporate feedback from trevor-scheer and rename article --- docs/gatsby-config.js | 2 +- .../{core-concepts.md => entities.md} | 56 +++++++++++-------- docs/static/_redirects | 3 +- 3 files changed, 35 insertions(+), 26 deletions(-) rename docs/source/federation/{core-concepts.md => entities.md} (69%) diff --git a/docs/gatsby-config.js b/docs/gatsby-config.js index 00082a78086..e082ea64225 100644 --- a/docs/gatsby-config.js +++ b/docs/gatsby-config.js @@ -38,7 +38,7 @@ module.exports = { 'Apollo Federation': [ 'federation/introduction', 'federation/implementing', - 'federation/core-concepts', + 'federation/entities', 'federation/value-types', 'federation/errors', 'federation/metrics', diff --git a/docs/source/federation/core-concepts.md b/docs/source/federation/entities.md similarity index 69% rename from docs/source/federation/core-concepts.md rename to docs/source/federation/entities.md index 08a343bcdfa..16537b9a6b1 100644 --- a/docs/source/federation/core-concepts.md +++ b/docs/source/federation/entities.md @@ -1,13 +1,11 @@ --- -title: Working with entities -description: Reference and extend another service's types +title: Entities +description: Reference and extend types across services --- In Apollo Federation, an **entity** is a type that you define canonically in _one_ implementing service and can then reference and extend in _other_ implementing services. Entities are the core building block of a federated graph. ->In a federated graph, every object that is _not_ an entity is a [value type](./value-types/). - -## Declaring +## Defining In a GraphQL schema, you can designate any object type as an entity by adding a `@key` directive to its definition, like so: @@ -19,7 +17,7 @@ type Product @key(fields: "upc") { } ``` -The `@key` directive declares the entity's **primary key**, which consists of one or more of the type's `fields`. In the example above, the `Product` entity's primary key is its `upc` field. Apollo Gateway's query planner uses an entity's primary key to identify a given instance of the type. +The `@key` directive defines the entity's **primary key**, which consists of one or more of the type's `fields`. In the example above, the `Product` entity's primary key is its `upc` field. The gateway's query planner uses an entity's primary key to identify a given instance of the type. > An entity's `@key` cannot include fields that hold unions or interfaces. @@ -39,8 +37,6 @@ type Product @key(fields: "upc") @key(fields: "sku") { This pattern is helpful when different services interact with different fields of an entity. For example, a `reviews` service might refer to products by their UPC, whereas an `inventory` service might use SKUs. -> You cannot list multiple primary keys when you [extend an entity](#extending), only when you declare the entity in its originating service. - ### Defining a compound primary key A single primary key can consist of multiple fields, and even nested fields. @@ -76,7 +72,7 @@ extend type Product @key(fields: "upc") { Because the `Product` entity is defined in another service, the `reviews` service needs to define a **stub** of it to make its own schema valid. The stub includes just enough information for the service to know how to interact with a `Product`: * The `extend` keyword indicates that `Product` is an entity that is defined in another implementing service (in this case, the `products` service). -* The `@key` directive indicates that `Product` uses the `upc` field as its primary key. **This value must match the value of a `@key` specified in the entity's originating service.** +* The `@key` directive indicates that `Product` uses the `upc` field as its primary key. This value must match the value of exactly one `@key` defined in the entity's originating service, even if the entity defines [multiple primary keys](#defining-multiple-primary-keys). * The `upc` field must be included in the stub because it is part of the specified `@key`. It also must be annotated with the `@external` directive to indicate that the field originates in another service. This explicit syntax has several benefits: @@ -100,7 +96,7 @@ In our example, the `reviews` service needs to define its own resolver for the ` This return value is a **representation** of a `Product` entity. Services use representations to reference entities from other services. A representation requires only an explicit `__typename` definition and values for the entity's primary key fields. -Apollo Gateway provides this representation to the entity's originating service to fetch the full object. For this to work, the originating service (in this case, `products`) must define a **reference resolver** for the `Product` entity: +The gateway provides this representation to the entity's originating service to fetch the full object. For this to work, the originating service (in this case, `products`) must define a **reference resolver** for the `Product` entity: ```js{3-5} { @@ -131,9 +127,9 @@ With this model, each implementing service's schema represents a true subset of An implementing service can add fields to an entity that's defined in another service. This is called **extending** the entity. -When a service extends an entity, the entity's _originating_ service is not aware of the added fields. Only the _extending_ service (along with Apollo Gateway) knows about these fields. +When a service extends an entity, the entity's _originating_ service is not aware of the added fields. Only the _extending_ service (along with the gateway) knows about these fields. -> It is invalid for more than one service to extend the same entity with the same field name. Each individual field of an entity must originate in exactly one service. +> Each field of an entity should be defined in exactly one service. Otherwise, a schema composition error will occur. ### Example #1 @@ -150,7 +146,7 @@ extend type Product @key(fields: "upc") { This definition is nearly identical to the stub we defined for the `Product` type in [Referencing](#referencing). All we've added is the `reviews` field. We _don't_ include an `@external` directive, because this field _does_ originate in the `reviews` service. -Whenever a service extends an entity with a new field, it is also responsible for _resolving_ the field. Apollo Gateway is automatically aware of this responsibility. In our example, the generated query plan will fetch the `upc` field for each `Product` from the `products` service and pass those to the `reviews` service, where you can then access these fields on the object passed into your `reviews` resolver: +Whenever a service extends an entity with a new field, it is also responsible for _resolving_ the field. The gateway is automatically aware of this responsibility. In our example, the generated query plan will fetch the `upc` field for each `Product` from the `products` service and pass those to the `reviews` service, where you can then access these fields on the object passed into your `reviews` resolver: ```js { @@ -183,7 +179,7 @@ extend type Product @key(fields: "upc") { } ``` -Similar to the `reviews` relationship example above, Apollo Gateway fetches the required `upc` field from the `products` service and passes it to the `inventory` service, even if the query didn't ask for the `upc`: +Similar to the `reviews` relationship example above, the gateway fetches the required `upc` field from the `products` service and passes it to the `inventory` service, even if the query didn't ask for the `upc`: ```graphql query { @@ -193,9 +189,9 @@ query { } ``` -## The `Query` and `Mutation` entities +## The `Query` and `Mutation` types -In Apollo Federation, the `Query` and `Mutation` base types are _automatically_ entities that originate in Apollo Gateway itself. Consequently, _all_ of your implementing services should [extend](#extending) these types to add the operations they support. +In Apollo Federation, the `Query` and `Mutation` base types originate in the gateway itself. Consequently, _all_ of your implementing services should [extend](#extending) these types to add the operations they support. For example, the `products` service might extend the root `Query` type to add a `topProducts` query, like so: @@ -209,19 +205,31 @@ extend type Query { As your federated graph grows, you might decide that you want a particular field of an entity to originate in a different service. -For example, let's say the `products` service defines a `Product` entity that includes an `inStock` boolean field. Then, you add an `inventory` service to your federated graph. It now makes sense for the `inStock` field to originate in the `inventory` service instead. +For example, let's say your `products` service defines a `Product` entity that includes an `inStock` boolean field. Then, you add an `inventory` service to your federated graph. It now makes sense for the `inStock` field to originate in the `inventory` service instead. Apollo Gateway helps you perform this migration much like you perform a database migration, with the following steps: -1. In the `inventory` service, [extend](#extending) the `Product` entity to add the `inStock` field. **Note that this is technically a federation error**, because this field is already defined in the `products` service. +1. In the `inventory` service's schema, [extend](#extending) the `Product` entity to add the `inStock` field. + + _Note that this is technically a composition error, because `inStock` is already defined in the `products` service. However, this error is handled gracefully, as described below._ 2. In the `inventory` service, add a resolver for the `inStock` field. This service should resolve the field with the exact same outcome as the resolver in the `products` service. -3. Push the updated `inventory` service to your environment. **Again, this is a federation error**. When Apollo Gateway detects a duplicate field like this, it ignores the _newer_ declaration (i.e., the one in the `inventory` service) and continues to resolve the field in the `products` service. +3. Push the updated `inventory` service to your environment. + + _Again, this technically deploys a composition error. **However**, this error is handled gracefully in one of two ways, depending on whether you are using [managed federation](https://www.apollographql.com/docs/graph-manager/federation/):_ + + * _If you **are** using managed federation, Graph Manager does **not** push an updated schema to your gateway, and the gateway continues to resolve the `inStock` field in the `products` service._ + + * _If you are **not** using managed federation, your gateway starts resolving the `inStock` field in whichever service is listed **last** in your gateway's [`serviceList`](/api/apollo-gateway/#apollogateway)._ + +4. In the `products` service's schema, remove the `inStock` field and push the updated service to your environment. + + _This takes care of the composition error, regardless of whether you are using managed federation. The gateway will begin resolving the `inStock` field in the `inventory` service._ -4. In the `products` service, remove the `inStock` field and its resolver. +5. Remove the resolver for `inStock` from the `products` service and push the updated service to your environment. -5. Push the updated `products` service to your environment. This removes the invalid duplicate, and Apollo Gateway will begin resolving the `inStock` field in the `inventory` service. + _By removing the `inStock` field from `products` **before** removing its associated resolver, you guarantee that the gateway never attempts to resolve the field in a service that lacks a resolver for it._ ## Extending an entity with computed fields (advanced) @@ -240,7 +248,7 @@ As shown, you use the `@requires` directive to indicate which fields (and subfie >You **cannot** require fields that are defined in a service besides the entity's originating service. -In the above example, if a client requests a product's `shippingEstimate`, Apollo Gateway will first obtain the product's `size` and `weight` from the `products` service, then pass those values to the `shipping` service. This enables you to access those values directly from your resolver: +In the above example, if a client requests a product's `shippingEstimate`, the gateway will first obtain the product's `size` and `weight` from the `products` service, then pass those values to the `shipping` service. This enables you to access those values directly from your resolver: ```js{4} { @@ -259,7 +267,7 @@ Sometimes, multiple implementing services are capable of resolving a particular When you [extend an entity](#extending) in this case, you can specify that the extending service `@provides` the field, like so: ```graphql{2,8-9}:title=inventory -type InStockCont { +type InStockCount { product: Product! @provides(fields: "name price") quantity: Integer! } @@ -271,7 +279,7 @@ extend type Product @key(fields: "sku") { } ``` -**This is a completely optional optimization.** When Apollo Gateway plans a query's execution, it looks at which fields are available from each implementing service. It then attempts to optimize performance by executing the query across the fewest services needed to access all required fields. +**This is a completely optional optimization.** When the gateway plans a query's execution, it looks at which fields are available from each implementing service. It can then attempt to optimize performance by executing the query across the fewest services needed to access all required fields. Keep the following in mind when using the `@provides` directive: diff --git a/docs/static/_redirects b/docs/static/_redirects index d5afb95ecc8..4862a66cda3 100644 --- a/docs/static/_redirects +++ b/docs/static/_redirects @@ -1,6 +1,7 @@ / /docs/apollo-server/ -/docs/apollo-server/federation/advanced-features/ /docs/apollo-server/federation/core-concepts/ +/docs/apollo-server/federation/core-concepts/ /docs/apollo-server/federation/entities/ +/docs/apollo-server/federation/advanced-features/ /docs/apollo-server/federation/entities/ /docs/apollo-server/federation/concerns/ /docs/apollo-server/federation/introduction/#separation-of-concerns # The test-utils lived at its current home for about 5 days. From 91282b85f1e9a6676380bfe8f215b03c62869880 Mon Sep 17 00:00:00 2001 From: Stephen Barlow Date: Mon, 24 Feb 2020 13:50:15 -0800 Subject: [PATCH 10/10] Fix another broken link --- docs/source/federation/implementing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/federation/implementing.md b/docs/source/federation/implementing.md index 3d55a582f9f..0afbe9885ad 100644 --- a/docs/source/federation/implementing.md +++ b/docs/source/federation/implementing.md @@ -114,7 +114,7 @@ const resolvers = { We would then define the `fetchUserById` function to obtain the appropriate `User` from our backing data store. -> See [Core concepts](./core-concepts/) for more information on working with entities. +> [Learn more about entities](./entities/) ### Generating a federated schema