diff --git a/CHANGELOG.md b/CHANGELOG.md index c6a72c1e7cd..794801e7fc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ The version headers in this history reflect the versions of Apollo Server itself ### v2.12.0 +- `apollo-server-core`: When operating in gateway mode using the `gateway` property of the Apollo Server constructor options, the failure to initialize a schema during initial start-up, e.g. connectivity problems, will no longer result in the federated executor from being assigned when the schema eventually becomes available. This precludes a state where the gateway may never become available to serve federated requests, even when failure conditions are no longer present. [PR #3811](https://github.com/apollographql/apollo-server/pull/3811) +- `apollo-server-core`: Prevent a condition which prefixed an error message on each request when the initial gateway initialization resulted in a Promise-rejection which was memoized and re-prepended with `Invalid options provided to ApolloServer:` on each request. [PR #3811](https://github.com/apollographql/apollo-server/pull/3811) + ### v2.11.0 - The range of accepted `peerDepedencies` versions for `graphql` has been widened to include `graphql@^15.0.0-rc.2` so as to accommodate the latest release-candidate of the `graphql@15` package, and an intention to support it when it is finally released on the `latest` npm tag. While this change will subdue peer dependency warnings for Apollo Server packages, many dependencies from outside of this repository will continue to raise similar warnings until those packages own `peerDependencies` are updated. It is unlikely that all of those packages will update their ranges prior to the final version of `graphql@15` being released, but if everything is working as expected, the warnings can be safely ignored. [PR #3825](https://github.com/apollographql/apollo-server/pull/3825) diff --git a/docs/package-lock.json b/docs/package-lock.json index 085581d9fa8..7ee058e28cb 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -1889,9 +1889,9 @@ "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" }, "@babel/types": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", - "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.7.tgz", + "integrity": "sha512-k2TreEHxFA4CjGkL+GYjRyx35W0Mr7DP5+9q6WMkyKXB+904bYmG40syjMFV0oLlhhFCwWl0vA0DyzTDkwAiJw==", "requires": { "esutils": "^2.0.2", "lodash": "^4.17.13", @@ -2061,9 +2061,9 @@ } }, "@babel/plugin-transform-typescript": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.8.3.tgz", - "integrity": "sha512-Ebj230AxcrKGZPKIp4g4TdQLrqX95TobLUWKd/CwG7X1XHUH1ZpkpFvXuXqWbtGRWb7uuEWNlrl681wsOArAdQ==", + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.8.7.tgz", + "integrity": "sha512-7O0UsPQVNKqpHeHLpfvOG4uXmlw+MOxYvUv6Otc9uH5SYMIxvF6eBdjkWvC3f9G+VXe0RsNExyAQBeTRug/wqQ==", "requires": { "@babel/helper-create-class-features-plugin": "^7.8.3", "@babel/helper-plugin-utils": "^7.8.3", @@ -2369,9 +2369,9 @@ } }, "@emotion/cache": { - "version": "10.0.27", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.27.tgz", - "integrity": "sha512-Zp8BEpbMunFsTcqAK4D7YTm3MvCp1SekflSLJH8lze2fCcSZ/yMkXHo8kb3t1/1Tdd3hAqf3Fb7z9VZ+FMiC9w==", + "version": "10.0.29", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz", + "integrity": "sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==", "requires": { "@emotion/sheet": "0.9.4", "@emotion/stylis": "0.8.5", @@ -2403,9 +2403,9 @@ } }, "@emotion/hash": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.4.tgz", - "integrity": "sha512-fxfMSBMX3tlIbKUdtGKxqB1fyrH6gVrX39Gsv3y8lRYKUqlgDt3UMqQyGnR1bQMa2B8aGnhLZokZgg8vT0Le+A==" + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" }, "@emotion/is-prop-valid": { "version": "0.8.7", @@ -2421,11 +2421,11 @@ "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" }, "@emotion/serialize": { - "version": "0.11.15", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.15.tgz", - "integrity": "sha512-YE+qnrmGwyR+XB5j7Bi+0GT1JWsdcjM/d4POu+TXkcnrRs4RFCCsi3d/Ebf+wSStHqAlTT2+dfd+b9N9EO2KBg==", + "version": "0.11.16", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.16.tgz", + "integrity": "sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg==", "requires": { - "@emotion/hash": "0.7.4", + "@emotion/hash": "0.8.0", "@emotion/memoize": "0.7.4", "@emotion/unitless": "0.7.5", "@emotion/utils": "0.11.3", @@ -2618,9 +2618,9 @@ } }, "@babel/types": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", - "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.7.tgz", + "integrity": "sha512-k2TreEHxFA4CjGkL+GYjRyx35W0Mr7DP5+9q6WMkyKXB+904bYmG40syjMFV0oLlhhFCwWl0vA0DyzTDkwAiJw==", "requires": { "esutils": "^2.0.2", "lodash": "^4.17.13", @@ -3916,14 +3916,14 @@ } }, "babel-plugin-emotion": { - "version": "10.0.28", - "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.0.28.tgz", - "integrity": "sha512-h25EMmPxYVNOgsEkGIjCv2Ok+HzW/e/b5lf2v2U17T9k6y6g0ku3TG9b+jy94ZrqMh+b/njRF4uOQrwVr28QfQ==", + "version": "10.0.29", + "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.0.29.tgz", + "integrity": "sha512-7Jpi1OCxjyz0k163lKtqP+LHMg5z3S6A7vMBfHnF06l2unmtsOmFDzZBpGf0CWo1G4m8UACfVcDJiSiRuu/cSw==", "requires": { "@babel/helper-module-imports": "^7.0.0", - "@emotion/hash": "0.7.4", + "@emotion/hash": "0.8.0", "@emotion/memoize": "0.7.4", - "@emotion/serialize": "^0.11.15", + "@emotion/serialize": "^0.11.16", "babel-plugin-macros": "^2.0.0", "babel-plugin-syntax-jsx": "^6.18.0", "convert-source-map": "^1.5.0", @@ -5205,9 +5205,9 @@ "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" }, "clipboard": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz", - "integrity": "sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.6.tgz", + "integrity": "sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg==", "optional": true, "requires": { "good-listener": "^1.2.2", @@ -9393,21 +9393,26 @@ } }, "gatsby-plugin-emotion": { - "version": "4.1.22", - "resolved": "https://registry.npmjs.org/gatsby-plugin-emotion/-/gatsby-plugin-emotion-4.1.22.tgz", - "integrity": "sha512-XG9YpkyUgbTHs/Uq7W6tDVDVQ2XHlj9rHPhCYiZHlJTdrJIHhviousHZ8+vEI/h0FQ4oW/Hs0CuX2gi5SlvWSQ==", + "version": "4.1.23", + "resolved": "https://registry.npmjs.org/gatsby-plugin-emotion/-/gatsby-plugin-emotion-4.1.23.tgz", + "integrity": "sha512-SP3hGbyj2Kq42iIS9tDR6aZMvBsbH7GhPizfmr+1L1KxYjFedjd3U/gWa346wJbvtiwnSkeoLZKMUATX4w1VCA==", "requires": { "@babel/runtime": "^7.7.6", "@emotion/babel-preset-css-prop": "^10.0.23" }, "dependencies": { "@babel/runtime": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz", - "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==", + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz", + "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==", "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } + }, + "regenerator-runtime": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz", + "integrity": "sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g==" } } }, @@ -9421,19 +9426,24 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz", - "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==", + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz", + "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==", "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } + }, + "regenerator-runtime": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz", + "integrity": "sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g==" } } }, "gatsby-plugin-mdx": { - "version": "1.0.74", - "resolved": "https://registry.npmjs.org/gatsby-plugin-mdx/-/gatsby-plugin-mdx-1.0.74.tgz", - "integrity": "sha512-mN+68a1qmsNEC6zDDUFCbEUJ2a/1E7S7tlidFUy7doOAMVYkpvfJBpBI3IPoGHiIaOIreo0jj7BRDI6NA9nlxQ==", + "version": "1.0.75", + "resolved": "https://registry.npmjs.org/gatsby-plugin-mdx/-/gatsby-plugin-mdx-1.0.75.tgz", + "integrity": "sha512-CMjAk8EbQh7uufWenUG4yQJ7fa7Jitp8txJmlIwxm4+xgKlrMvmJ9fg5PhDFlCjG8t09mxoJt8dlh8izJZXQwg==", "requires": { "@babel/core": "^7.7.5", "@babel/generator": "^7.7.4", @@ -9473,9 +9483,9 @@ }, "dependencies": { "@babel/types": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", - "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.7.tgz", + "integrity": "sha512-k2TreEHxFA4CjGkL+GYjRyx35W0Mr7DP5+9q6WMkyKXB+904bYmG40syjMFV0oLlhhFCwWl0vA0DyzTDkwAiJw==", "requires": { "esutils": "^2.0.2", "lodash": "^4.17.13", @@ -9556,12 +9566,17 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz", - "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==", + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz", + "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==", "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } + }, + "regenerator-runtime": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz", + "integrity": "sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g==" } } }, @@ -9616,13 +9631,18 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz", - "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==", + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz", + "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==", "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } }, + "regenerator-runtime": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz", + "integrity": "sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g==" + }, "unist-util-visit": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", @@ -9651,6 +9671,44 @@ } } }, + "gatsby-remark-code-titles": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gatsby-remark-code-titles/-/gatsby-remark-code-titles-1.1.0.tgz", + "integrity": "sha512-RuNqziXi99eBIj5NJP0TgdzAxzWFL+ArGRb3961Ff9Tto/nCvmyqR1qySaWKXtkOgeqoVUlqAFNUCyEAyNuc8w==", + "requires": { + "query-string": "~6.0.0", + "unist-util-visit": "~1.3.0" + }, + "dependencies": { + "query-string": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.0.0.tgz", + "integrity": "sha1-i485RHtz6CkNb141gXeSGOkXEUI=", + "requires": { + "decode-uri-component": "^0.2.0", + "strict-uri-encode": "^2.0.0" + } + }, + "strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=" + }, + "unist-util-is": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-2.1.3.tgz", + "integrity": "sha512-4WbQX2iwfr/+PfM4U3zd2VNXY+dWtZsN1fLnWEi2QQXA4qyDYAZcDMfXUX0Cu6XZUHHAO9q4nyxxLT4Awk1qUA==" + }, + "unist-util-visit": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.3.1.tgz", + "integrity": "sha512-0fdB9EQJU0tho5tK0VzOJzAQpPv2LyLZ030b10GxuzAWEfvd54mpY7BMjQ1L69k2YNvL+SvxRzH0yUIehOO8aA==", + "requires": { + "unist-util-is": "^2.1.1" + } + } + } + }, "gatsby-remark-copy-linked-files": { "version": "2.1.37", "resolved": "https://registry.npmjs.org/gatsby-remark-copy-linked-files/-/gatsby-remark-copy-linked-files-2.1.37.tgz", @@ -9667,11 +9725,11 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz", - "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==", + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz", + "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==", "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } }, "cheerio": { @@ -9709,6 +9767,11 @@ "@types/node": "*" } }, + "regenerator-runtime": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz", + "integrity": "sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g==" + }, "unist-util-visit": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", @@ -9740,9 +9803,9 @@ } }, "gatsby-remark-prismjs": { - "version": "3.3.31", - "resolved": "https://registry.npmjs.org/gatsby-remark-prismjs/-/gatsby-remark-prismjs-3.3.31.tgz", - "integrity": "sha512-n6tczCq/w5LazZ5yk9UXu/6YjyLR7p1rQbBxqgkOL1xEFRmQcK5BwFhcpmCh5OKiqWBvqLDJq561UIFL0jcI/A==", + "version": "3.3.32", + "resolved": "https://registry.npmjs.org/gatsby-remark-prismjs/-/gatsby-remark-prismjs-3.3.32.tgz", + "integrity": "sha512-n/9VLOs5xNOgGQj4m1//PVmvQLEgbmLPqQo5/Hmuw4b+x76KFHfZGVrvwUHpSB0/yCrv6UCykOFI5J8ZxPXjkg==", "requires": { "@babel/runtime": "^7.7.6", "parse-numeric-range": "^0.0.2", @@ -9750,31 +9813,18 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz", - "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==", + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz", + "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==", "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } }, - "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" - } - } - } - }, - "gatsby-remark-prismjs-title": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gatsby-remark-prismjs-title/-/gatsby-remark-prismjs-title-1.0.0.tgz", - "integrity": "sha512-VKAw7LGAbzyDlztUfhOri+jDTjLyOPCJCNgkdt2+61+SP8M9wYzzma8NvVyzHP7J9hx0jcYq8F50XQH5dE42ow==", - "requires": { - "unist-util-visit": "~1.4.0" - }, - "dependencies": { + "regenerator-runtime": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz", + "integrity": "sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g==" + }, "unist-util-visit": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", @@ -9816,17 +9866,22 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz", - "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==", + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz", + "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==", "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "regenerator-runtime": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz", + "integrity": "sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g==" } } }, @@ -9981,9 +10036,9 @@ } }, "gatsby-theme-apollo-core": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/gatsby-theme-apollo-core/-/gatsby-theme-apollo-core-3.0.9.tgz", - "integrity": "sha512-P6Y+uB25VdX72cfsjRb6r1aJYAG2Wnqrgq/goXbaADliikfoJIV41Ld5v1IdH15KFGD0AMHdNFt9y93Q90uiYg==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/gatsby-theme-apollo-core/-/gatsby-theme-apollo-core-3.0.10.tgz", + "integrity": "sha512-vXh1Yu1H2mdDCtuoc6UVGhXLwxXzN4YmWgnweEW+e1ZQYH1WKYQhMM/XjuUQUxXz0aI34y7IZeL3rvXUiDhh9Q==", "requires": { "@apollo/space-kit": "2.15.0", "@emotion/core": "^10.0.7", @@ -10002,9 +10057,9 @@ } }, "gatsby-theme-apollo-docs": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/gatsby-theme-apollo-docs/-/gatsby-theme-apollo-docs-4.0.10.tgz", - "integrity": "sha512-w0kc1c1JI5JuxwyE3AhGEUwftGoaPXhFXeWFWp+QezvHs8YENzpI0pByQrC4Y6iqU09GuTuOktlc/Hi9pObcdQ==", + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/gatsby-theme-apollo-docs/-/gatsby-theme-apollo-docs-4.0.13.tgz", + "integrity": "sha512-584b21cJmmT/7WyEoSKgxLRJUtRsLrapQWThxRUN8+pQMn4rv+jzAfkOwwNf1l7ahbKRWrA1Gnb/hF0bmaOuAw==", "requires": { "@mdx-js/mdx": "^1.1.0", "@mdx-js/react": "^1.0.27", @@ -10013,14 +10068,14 @@ "gatsby-plugin-segment-js": "^3.0.1", "gatsby-remark-autolink-headers": "^2.0.16", "gatsby-remark-check-links": "^2.1.0", + "gatsby-remark-code-titles": "^1.1.0", "gatsby-remark-copy-linked-files": "^2.0.12", "gatsby-remark-mermaid": "^1.2.0", "gatsby-remark-prismjs": "^3.2.8", - "gatsby-remark-prismjs-title": "^1.0.0", "gatsby-remark-rewrite-relative-links": "^1.0.7", "gatsby-source-filesystem": "^2.0.29", "gatsby-source-git": "^1.0.1", - "gatsby-theme-apollo-core": "^3.0.9", + "gatsby-theme-apollo-core": "^3.0.10", "gatsby-transformer-remark": "^2.6.30", "js-yaml": "^3.13.1", "prismjs": "^1.15.0", @@ -10063,11 +10118,11 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz", - "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==", + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz", + "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==", "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } }, "bluebird": { @@ -10204,6 +10259,11 @@ "xtend": "^4.0.1" } }, + "regenerator-runtime": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz", + "integrity": "sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g==" + }, "remark-parse": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-6.0.3.tgz", @@ -15671,9 +15731,9 @@ } }, "proxy-from-env": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", - "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "prr": { "version": "1.0.1", @@ -16754,9 +16814,9 @@ } }, "@babel/types": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", - "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.7.tgz", + "integrity": "sha512-k2TreEHxFA4CjGkL+GYjRyx35W0Mr7DP5+9q6WMkyKXB+904bYmG40syjMFV0oLlhhFCwWl0vA0DyzTDkwAiJw==", "requires": { "esutils": "^2.0.2", "lodash": "^4.17.13", @@ -19872,9 +19932,9 @@ } }, "vfile": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.0.2.tgz", - "integrity": "sha512-yhoTU5cDMSsaeaMfJ5g0bUKYkYmZhAh9fn9TZicxqn+Cw4Z439il2v3oT9S0yjlpqlI74aFOQCt3nOV+pxzlkw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.0.3.tgz", + "integrity": "sha512-lREgT5sF05TQk68LO6APy0In+TkFGnFEgKChK2+PHIaTrFQ9oHCKXznZ7VILwgYVBcl0gv4lGATFZBLhi2kVQg==", "requires": { "@types/unist": "^2.0.0", "is-buffer": "^2.0.0", @@ -19896,9 +19956,9 @@ "integrity": "sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==" }, "vfile-message": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.2.tgz", - "integrity": "sha512-gNV2Y2fDvDOOqq8bEe7cF3DXU6QgV4uA9zMR2P8tix11l1r7zju3zry3wZ8sx+BEfuO6WQ7z2QzfWTvqHQiwsA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.3.tgz", + "integrity": "sha512-qQg/2z8qnnBHL0psXyF72kCjb9YioIynvyltuNKFaUhRtqTIcIMP3xnBaPzirVZNuBrUe1qwFciSx2yApa4byw==", "requires": { "@types/unist": "^2.0.0", "unist-util-stringify-position": "^2.0.0" diff --git a/docs/package.json b/docs/package.json index 3015a90ab59..1d8e7335bf0 100644 --- a/docs/package.json +++ b/docs/package.json @@ -7,7 +7,7 @@ }, "dependencies": { "gatsby": "2.19.23", - "gatsby-theme-apollo-docs": "4.0.10", + "gatsby-theme-apollo-docs": "4.0.13", "react": "16.13.0", "react-dom": "16.13.0" } diff --git a/packages/apollo-federation/CHANGELOG.md b/packages/apollo-federation/CHANGELOG.md index a4f2d9e93dc..d78c53f24bc 100644 --- a/packages/apollo-federation/CHANGELOG.md +++ b/packages/apollo-federation/CHANGELOG.md @@ -1,6 +1,10 @@ # CHANGELOG for `@apollo/federation` -## 0.13.2 (pre-release; `@next` tag) +## 0.14.0 + +- Only changes in the similarly versioned `@apollo/gateway` package. + +## 0.13.2 - Only changes in the similarly versioned `@apollo/gateway` package. diff --git a/packages/apollo-gateway/CHANGELOG.md b/packages/apollo-gateway/CHANGELOG.md index 7cfaed85024..b29578cbd08 100644 --- a/packages/apollo-gateway/CHANGELOG.md +++ b/packages/apollo-gateway/CHANGELOG.md @@ -1,6 +1,12 @@ -# CHANGELOG for `@apollo/gateway` +# CHANGELOG for `@apollo/gatewae` -## 0.13.2 (pre-release; `@next` tag) +## 0.14.0 (pre-release; `@next` tag) + +- Several previously unhandled Promise rejection errors stemming from, e.g. connectivity, failures when communicating with Apollo Graph Manager within asynchronous code are now handled. [PR #3811](https://github.com/apollographql/apollo-server/pull/3811) +- Provide a more helpful error message when encountering expected errors. [PR #3811](https://github.com/apollographql/apollo-server/pull/3811) +- General improvements and clarity to error messages and logging. [PR #3811](https://github.com/apollographql/apollo-server/pull/3811) + +## 0.13.2 - __BREAKING__: The behavior and signature of `RemoteGraphQLDataSource`'s `didReceiveResponse` method has been changed. No changes are necessary _unless_ your implementation has overridden the default behavior of this method by either extending the class and overriding the method or by providing `didReceiveResponse` as a parameter to the `RemoteGraphQLDataSource`'s constructor options. Implementations which have provided their own `didReceiveResponse` using either of these methods should view the PR linked here for details on what has changed. [PR #3743](https://github.com/apollographql/apollo-server/pull/3743) - __NEW__: Setting the `apq` option to `true` on the `RemoteGraphQLDataSource` will enable the use of [automated persisted queries (APQ)](https://www.apollographql.com/docs/apollo-server/performance/apq/) when sending queries to downstream services. Depending on the complexity of queries sent to downstream services, this technique can greatly reduce the size of the payloads being transmitted over the network. Downstream implementing services must also support APQ functionality to participate in this feature (Apollo Server does by default unless it has been explicitly disabled). As with normal APQ behavior, a downstream server must have received and registered a query once before it will be able to serve an APQ request. [#3744](https://github.com/apollographql/apollo-server/pull/3744) diff --git a/packages/apollo-gateway/src/__tests__/gateway/executor.test.ts b/packages/apollo-gateway/src/__tests__/gateway/executor.test.ts index 9d73387ab14..7e8a7490f64 100644 --- a/packages/apollo-gateway/src/__tests__/gateway/executor.test.ts +++ b/packages/apollo-gateway/src/__tests__/gateway/executor.test.ts @@ -6,6 +6,7 @@ import * as books from '../__fixtures__/schemas/books'; import * as inventory from '../__fixtures__/schemas/inventory'; import * as product from '../__fixtures__/schemas/product'; import * as reviews from '../__fixtures__/schemas/reviews'; +import { ApolloServer } from "apollo-server"; describe('ApolloGateway executor', () => { it('validates requests prior to execution', async () => { @@ -35,4 +36,24 @@ describe('ApolloGateway executor', () => { 'Variable "$first" got invalid value "3"; Expected type Int.', ); }); + + it('still sets the ApolloServer executor on load rejection', async () => { + jest.spyOn(console, 'error').mockImplementation(); + + const gateway = new ApolloGateway({ + // Empty service list will throw, which is what we want. + serviceList: [], + }); + + const server = new ApolloServer({ + gateway, + subscriptions: false, + }); + + // Ensure the throw happens to maintain the correctness of this test. + await expect( + server.executeOperation({ query: '{ __typename }' })).rejects.toThrow(); + + expect(server.requestOptions.executor).toBe(gateway.executor); + }); }); diff --git a/packages/apollo-gateway/src/__tests__/gateway/lifecycle-hooks.test.ts b/packages/apollo-gateway/src/__tests__/gateway/lifecycle-hooks.test.ts index 3b3f31dd692..267c15cc984 100644 --- a/packages/apollo-gateway/src/__tests__/gateway/lifecycle-hooks.test.ts +++ b/packages/apollo-gateway/src/__tests__/gateway/lifecycle-hooks.test.ts @@ -56,9 +56,7 @@ describe('lifecycle hooks', () => { experimental_didFailComposition, }); - try { - await gateway.load(); - } catch {} + await expect(gateway.load()).rejects.toThrowError(); const callbackArgs = experimental_didFailComposition.mock.calls[0][0]; expect(callbackArgs.serviceList).toHaveLength(1); diff --git a/packages/apollo-gateway/src/index.ts b/packages/apollo-gateway/src/index.ts index a34db0a362d..cfd7e033017 100644 --- a/packages/apollo-gateway/src/index.ts +++ b/packages/apollo-gateway/src/index.ts @@ -296,20 +296,16 @@ export class ApolloGateway implements GraphQLService { this.engineConfig = options.engine; } - const previousSchema = this.schema; - const previousServiceDefinitions = this.serviceDefinitions; - const previousCompositionMetadata = this.compositionMetadata; - let result: Await>; - this.logger.debug('Loading configuration for gateway'); + this.logger.debug('Checking service definitions...'); try { result = await this.updateServiceDefinitions(this.config); } catch (e) { - this.logger.warn( - 'Error checking for schema updates. Falling back to existing schema.', - e, + this.logger.error( + "Error checking for changes to service definitions: " + + (e && e.message || e) ); - return; + throw e; } if ( @@ -317,12 +313,16 @@ export class ApolloGateway implements GraphQLService { JSON.stringify(this.serviceDefinitions) === JSON.stringify(result.serviceDefinitions) ) { - this.logger.debug('No change in service definitions since last check'); + this.logger.debug('No change in service definitions since last check.'); return; } + const previousSchema = this.schema; + const previousServiceDefinitions = this.serviceDefinitions; + const previousCompositionMetadata = this.compositionMetadata; + if (previousSchema) { - this.logger.info('Gateway config has changed, updating schema'); + this.logger.info("New service definitions were found."); } this.compositionMetadata = result.compositionMetadata; @@ -335,9 +335,8 @@ export class ApolloGateway implements GraphQLService { this.onSchemaChangeListeners.forEach(listener => listener(this.schema!)); } catch (e) { this.logger.error( - 'Error notifying schema change listener of update to schema.', - e, - ); + "An error was thrown from an 'onSchemaChange' listener. " + + "The schema will still update: ", e); } if (this.experimental_didUpdateComposition) { @@ -415,8 +414,12 @@ export class ApolloGateway implements GraphQLService { private startPollingServices() { if (this.pollingTimer) clearInterval(this.pollingTimer); - this.pollingTimer = setInterval(() => { - this.updateComposition(); + this.pollingTimer = setInterval(async () => { + try { + await this.updateComposition(); + } catch (err) { + this.logger.error(err && err.message || err); + } }, this.experimental_pollInterval || 10000); // Prevent the Node.js event loop from remaining active (and preventing, diff --git a/packages/apollo-gateway/src/loadServicesFromRemoteEndpoint.ts b/packages/apollo-gateway/src/loadServicesFromRemoteEndpoint.ts index a11840c7911..829716ff8c4 100644 --- a/packages/apollo-gateway/src/loadServicesFromRemoteEndpoint.ts +++ b/packages/apollo-gateway/src/loadServicesFromRemoteEndpoint.ts @@ -29,7 +29,8 @@ export async function getServiceDefinitionsFromRemoteEndpoint({ const serviceDefinitions: ServiceDefinition[] = (await Promise.all( serviceList.map(({ name, url, dataSource }) => { if (!url) { - throw new Error(`Tried to load schema from ${name} but no url found`); + throw new Error( + `Tried to load schema for '${name}' but no 'url' was specified.`); } const request: GraphQLRequest = { diff --git a/packages/apollo-gateway/src/loadServicesFromStorage.ts b/packages/apollo-gateway/src/loadServicesFromStorage.ts index 89d8f5bf260..e1577a7e39a 100644 --- a/packages/apollo-gateway/src/loadServicesFromStorage.ts +++ b/packages/apollo-gateway/src/loadServicesFromStorage.ts @@ -50,6 +50,57 @@ function getStorageSecretUrl(graphId: string, apiKeyHash: string): string { return `${urlStorageSecretBase}/${graphId}/storage-secret/${apiKeyHash}.json`; } +function fetchApolloGcs( + fetcher: typeof fetch, + ...args: Parameters +): ReturnType { + const [input, init] = args; + + // Used in logging. + const url = typeof input === 'object' && input.url || input; + + return fetcher(input, init) + .catch(fetchError => { + throw new Error( + "Cannot access Apollo Graph Manager storage: " + fetchError) + }) + .then(async (response) => { + // If the fetcher has a cache and has implemented ETag validation, then + // a 304 response may be returned. Either way, we will return the + // non-JSON-parsed version and let the caller decide if that's important + // to their needs. + if (response.ok || response.status === 304) { + return response; + } + + // We won't make any assumptions that the body is anything but text, to + // avoid parsing errors in this unknown condition. + const body = await response.text(); + + // Google Cloud Storage returns an `application/xml` error under error + // conditions. We'll special-case our known errors, and resort to + // printing the body for others. + if ( + response.headers.get('content-type') === 'application/xml' && + response.status === 403 && + body.includes("AccessDenied") && + body.includes("Anonymous caller does not have storage.objects.get") + ) { + throw new Error( + "Unable to authenticate with Apollo Graph Manager storage " + + "while fetching " + url + ". Ensure that the API key is " + + "configured properly and that a federated service has been " + + "pushed. For details, see " + + "https://go.apollo.dev/g/resolve-access-denied."); + } + + // Normally, we'll try to keep the logs clean with errors we expect. + // If it's not a known error, reveal the full body for debugging. + throw new Error( + "Could not communicate with Apollo Graph Manager storage: " + body); + }); +}; + export async function getServiceDefinitionsFromStorage({ graphId, apiKeyHash, @@ -66,9 +117,9 @@ export async function getServiceDefinitionsFromStorage({ // fetch the storage secret const storageSecretUrl = getStorageSecretUrl(graphId, apiKeyHash); - const secret: string = await fetcher(storageSecretUrl).then(response => - response.json(), - ); + // The storage secret is a JSON string (e.g. `"secret"`). + const secret: string = + await fetchApolloGcs(fetcher, storageSecretUrl).then(res => res.json()); if (!graphVariant) { graphVariant = 'current'; @@ -76,17 +127,19 @@ export async function getServiceDefinitionsFromStorage({ const baseUrl = `${urlPartialSchemaBase}/${secret}/${graphVariant}/v${federationVersion}`; - const response = await fetcher(`${baseUrl}/composition-config-link`); + const compositionConfigResponse = + await fetchApolloGcs(fetcher, `${baseUrl}/composition-config-link`); - if (response.status === 304) { + if (compositionConfigResponse.status === 304) { return { isNewSchema: false }; } - const linkFileResult: LinkFileResult = await response.json(); + const linkFileResult: LinkFileResult = await compositionConfigResponse.json(); - const compositionMetadata: CompositionMetadata = await fetcher( + const compositionMetadata: CompositionMetadata = await fetchApolloGcs( + fetcher, `${urlPartialSchemaBase}/${linkFileResult.configPath}`, - ).then(response => response.json()); + ).then(res => res.json()); // It's important to maintain the original order here const serviceDefinitions = await Promise.all( diff --git a/packages/apollo-server-core/src/ApolloServer.ts b/packages/apollo-server-core/src/ApolloServer.ts index 6c21d422fc1..6d225b4c6cf 100644 --- a/packages/apollo-server-core/src/ApolloServer.ts +++ b/packages/apollo-server-core/src/ApolloServer.ts @@ -186,7 +186,6 @@ export class ApolloServerBase { } = config; if (gateway && (modules || schema || typeDefs || resolvers)) { - // TODO: this could be handled by adjusting the typings to keep gateway configs and non-gateway configs seprate. throw new Error( 'Cannot define both `gateway` and any of: `modules`, `schema`, `typeDefs`, or `resolvers`', ); @@ -417,13 +416,27 @@ export class ApolloServerBase { } : undefined; - return gateway.load({ engine: engineConfig }).then(config => { - this.requestOptions.executor = config.executor; - return config.schema; - }); + // Set the executor whether the gateway 'load' call succeeds or not. + // If the schema becomes available eventually (after a setInterval retry) + // this executor will still be necessary in order to be able to support + // a federated schema! + this.requestOptions.executor = gateway.executor; + + return gateway.load({ engine: engineConfig }) + .then(config => config.schema) + .catch(err => { + // We intentionally do not re-throw the exact error from the gateway + // configuration as it may contain implementation details and this + // error will propogate to the client. We will, however, log the error + // for observation in the logs. + const message = "This data graph is missing a valid configuration."; + console.error(message + " " + (err && err.message || err)); + throw new Error( + message + " More details may be available in the server logs."); + }); } - let constructedSchema; + let constructedSchema: GraphQLSchema; if (schema) { constructedSchema = schema; } else if (modules) { @@ -560,7 +573,20 @@ export class ApolloServerBase { } protected async willStart() { - const { schema, schemaHash } = await this.schemaDerivedData; + try { + var { schema, schemaHash } = await this.schemaDerivedData; + } catch (err) { + // The `schemaDerivedData` can throw if the Promise it points to does not + // resolve with a `GraphQLSchema`. As errors from `willStart` are start-up + // errors, other Apollo middleware after us will not be called, including + // our health check, CORS, etc. + // + // Returning here allows the integration's other Apollo middleware to + // function properly in the event of a failure to obtain the data graph + // configuration from the gateway's `load` method during initialization. + return; + } + const service: GraphQLServiceContext = { schema: schema, schemaHash: schemaHash, @@ -789,14 +815,7 @@ export class ApolloServerBase { } public async executeOperation(request: GraphQLRequest) { - let options; - - try { - options = await this.graphQLServerOptions(); - } catch (e) { - e.message = `Invalid options provided to ApolloServer: ${e.message}`; - throw new Error(e); - } + const options = await this.graphQLServerOptions(); if (typeof options.context === 'function') { options.context = (options.context as () => never)(); diff --git a/packages/apollo-server-core/src/runHttpQuery.ts b/packages/apollo-server-core/src/runHttpQuery.ts index 633c50daacd..c9d2d3ed0df 100644 --- a/packages/apollo-server-core/src/runHttpQuery.ts +++ b/packages/apollo-server-core/src/runHttpQuery.ts @@ -127,10 +127,6 @@ export async function runHttpQuery( // the normal options provided by the user, such as: formatError, // debug. Therefore, we need to do some unnatural things, such // as use NODE_ENV to determine the debug settings - e.message = `Invalid options provided to ApolloServer: ${e.message}`; - if (!debugDefault) { - e.warning = `To remove the stacktrace, set the NODE_ENV environment variable to production if the options creation can fail`; - } return throwHttpGraphQLError(500, [e], { debug: debugDefault }); } if (options.debug === undefined) { diff --git a/packages/apollo-server-core/src/types.ts b/packages/apollo-server-core/src/types.ts index 6eeb844a07f..ddd1652c9bd 100644 --- a/packages/apollo-server-core/src/types.ts +++ b/packages/apollo-server-core/src/types.ts @@ -5,7 +5,13 @@ import { IMocks, GraphQLParseOptions, } from 'graphql-tools'; -import { ValueOrPromise, GraphQLExecutor } from 'apollo-server-types'; +import { + ValueOrPromise, + GraphQLExecutor, + GraphQLExecutionResult, + WithRequired, + GraphQLRequestContext, +} from 'apollo-server-types'; import { ConnectionContext } from 'subscriptions-transport-ws'; // The types for `ws` use `export = WebSocket`, so we'll use the // matching `import =` to bring in its sole export. @@ -87,6 +93,14 @@ export interface GraphQLService { engine?: GraphQLServiceEngineConfig; }): Promise; onSchemaChange(callback: SchemaChangeCallback): Unsubscriber; + // Note: The `TContext` typing here is not conclusively behaving as we expect: + // https://github.com/apollographql/apollo-server/pull/3811#discussion_r387381605 + executor( + requestContext: WithRequired< + GraphQLRequestContext, + 'document' | 'queryHash' | 'operationName' | 'operation' + >, + ): ValueOrPromise; } // This configuration is shared between all integrations and should include diff --git a/packages/apollo-server-integration-testsuite/src/ApolloServer.ts b/packages/apollo-server-integration-testsuite/src/ApolloServer.ts index 05219c78d8b..176033dd152 100644 --- a/packages/apollo-server-integration-testsuite/src/ApolloServer.ts +++ b/packages/apollo-server-integration-testsuite/src/ApolloServer.ts @@ -116,24 +116,31 @@ const schema = new GraphQLSchema({ const makeGatewayMock = ({ optionsSpy = _options => {}, unsubscribeSpy = () => {}, + executor = () => ({}), }: { optionsSpy?: (_options: any) => void; unsubscribeSpy?: () => void; + executor?: GraphQLExecutor; } = {}) => { const eventuallyAssigned = { resolveLoad: null as ({ schema, executor }) => void, + rejectLoad: null as (err: Error) => void, triggerSchemaChange: null as (newSchema) => void, }; const mockedLoadResults = new Promise<{ schema: GraphQLSchema; executor: GraphQLExecutor; - }>(resolve => { + }>((resolve, reject) => { eventuallyAssigned.resolveLoad = ({ schema, executor }) => { resolve({ schema, executor }); }; + eventuallyAssigned.rejectLoad = (err: Error) => { + reject(err); + }; }); const mockedGateway: GraphQLService = { + executor, load: options => { optionsSpy(options); return mockedLoadResults; @@ -354,13 +361,13 @@ export function testApolloServer( }); it("accepts a gateway's schema and calls its executor", async () => { - const { gateway, triggers } = makeGatewayMock(); - const executor = jest.fn(); executor.mockReturnValue( Promise.resolve({ data: { testString: 'hi - but federated!' } }), ); + const { gateway, triggers } = makeGatewayMock({ executor }); + triggers.resolveLoad({ schema, executor }); const { url: uri } = await createApolloServer({ @@ -376,6 +383,47 @@ export function testApolloServer( expect(executor).toHaveBeenCalled(); }); + it("rejected load promise acts as an error boundary", async () => { + const executor = jest.fn(); + executor.mockResolvedValueOnce( + { data: { testString: 'should not get this' } } + ); + + executor.mockRejectedValueOnce( + { errors: [{errorWhichShouldNot: "ever be triggered"}] } + ); + + const consoleErrorSpy = + jest.spyOn(console, 'error').mockImplementation(); + + const { gateway, triggers } = makeGatewayMock({ executor }); + + triggers.rejectLoad(new Error("load error which should be masked")); + + const { url: uri } = await createApolloServer({ + gateway, + subscriptions: false, + }); + + const apolloFetch = createApolloFetch({ uri }); + const result = await apolloFetch({ query: '{testString}' }); + + expect(result.data).toBeUndefined(); + expect(result.errors).toContainEqual( + expect.objectContaining({ + extensions: expect.objectContaining({ + code: "INTERNAL_SERVER_ERROR", + }), + message: "This data graph is missing a valid configuration. " + + "More details may be available in the server logs." + }) + ); + expect(consoleErrorSpy).toHaveBeenCalledWith( + "This data graph is missing a valid configuration. " + + "load error which should be masked"); + expect(executor).not.toHaveBeenCalled(); + }); + it('uses schema over resolvers + typeDefs', async () => { const typeDefs = gql` type Query { @@ -2833,13 +2881,13 @@ export function testApolloServer( }), }); - const { gateway, triggers } = makeGatewayMock(); - const executor = req => (req.source as string).match(/1/) ? Promise.resolve({ data: { testString1: 'hello' } }) : Promise.resolve({ data: { testString2: 'aloha' } }); + const { gateway, triggers } = makeGatewayMock({ executor }); + triggers.resolveLoad({ schema: makeQueryTypeWithField('testString1'), executor, @@ -2895,7 +2943,6 @@ export function testApolloServer( }); it('waits until gateway has resolved a schema to respond to queries', async () => { - const { gateway, triggers } = makeGatewayMock(); const wait = ms => new Promise(resolve => setTimeout(resolve, ms)); let resolveExecutor; const executor = () => @@ -2905,6 +2952,8 @@ export function testApolloServer( }; }); + const { gateway, triggers } = makeGatewayMock({ executor }); + triggers.resolveLoad({ schema, executor }); const { url: uri } = await createApolloServer({ gateway, @@ -2941,8 +2990,6 @@ export function testApolloServer( }), }); - const { gateway, triggers } = makeGatewayMock(); - const makeEventuallyResolvingPromise = val => { let resolver; const promise = new Promise( @@ -2968,6 +3015,8 @@ export function testApolloServer( ? p2 : p3; + const { gateway, triggers } = makeGatewayMock({ executor }); + triggers.resolveLoad({ schema: makeQueryTypeWithField('testString1'), executor,