From 22286e4be827e9f5143614879912ae773b7b99cf Mon Sep 17 00:00:00 2001 From: Trevor Scheer Date: Mon, 13 Apr 2020 14:25:49 -0700 Subject: [PATCH] Revert "Merge pull request #3855 from apollographql/release-2.12.0" This reverts commit dd898bba81e3b4e8a2440ba9dff760ba098a0367, reversing changes made to 650f298617399c4d8cd06c109a53948930815bba. --- .circleci/config.yml | 16 +- CHANGELOG.md | 4 + docs/source/api/apollo-gateway.mdx | 34 +- docs/source/api/apollo-server.md | 10 - docs/source/integrations/plugins.md | 23 +- package-lock.json | 427 +----------------- package.json | 8 +- packages/apollo-cache-control/package.json | 4 +- packages/apollo-datasource-rest/package.json | 2 +- packages/apollo-engine-reporting/package.json | 4 +- packages/apollo-engine-reporting/src/agent.ts | 45 +- .../apollo-engine-reporting/src/extension.ts | 5 +- .../src/treeBuilder.ts | 31 +- packages/apollo-federation/CHANGELOG.md | 4 + packages/apollo-federation/package.json | 4 +- packages/apollo-gateway/CHANGELOG.md | 6 +- packages/apollo-gateway/package.json | 5 +- .../__tests__/gateway/buildService.test.ts | 10 +- .../src/__tests__/gateway/executor.test.ts | 21 - .../__tests__/gateway/lifecycle-hooks.test.ts | 4 +- .../src/__tests__/integration/logger.test.ts | 125 ----- .../integration/networkRequests.test.ts | 386 ++-------------- .../src/__tests__/integration/nockMocks.ts | 132 +++--- .../loadServicesFromRemoteEndpoint.test.ts | 35 -- .../apollo-gateway/src/executeQueryPlan.ts | 7 +- packages/apollo-gateway/src/index.ts | 275 +++-------- .../src/loadServicesFromRemoteEndpoint.ts | 88 ++-- .../src/loadServicesFromStorage.ts | 69 +-- .../package.json | 4 +- .../apollo-server-cache-redis/package.json | 2 +- .../src/RedisCache.ts | 4 +- .../src/RedisClusterCache.ts | 6 +- .../package.json | 4 +- .../apollo-server-cloudflare/package.json | 4 +- packages/apollo-server-core/package.json | 5 +- .../apollo-server-core/src/ApolloServer.ts | 102 +---- .../src/__tests__/logger.test.ts | 137 ------ .../apollo-server-core/src/graphqlOptions.ts | 3 - .../apollo-server-core/src/requestPipeline.ts | 84 ++-- .../apollo-server-core/src/runHttpQuery.ts | 10 +- packages/apollo-server-core/src/types.ts | 13 +- packages/apollo-server-errors/package.json | 4 +- packages/apollo-server-express/package.json | 4 +- packages/apollo-server-fastify/package.json | 4 +- packages/apollo-server-hapi/package.json | 4 +- .../package.json | 2 +- .../src/ApolloServer.ts | 65 +-- packages/apollo-server-koa/package.json | 4 +- packages/apollo-server-lambda/package.json | 4 +- packages/apollo-server-micro/package.json | 2 +- .../apollo-server-plugin-base/package.json | 4 +- .../apollo-server-plugin-base/src/index.ts | 58 ++- .../package.json | 4 +- .../src/ApolloServerPluginResponseCache.ts | 6 +- packages/apollo-server-testing/package.json | 4 +- packages/apollo-server-types/package.json | 4 +- packages/apollo-server-types/src/index.ts | 59 +-- packages/apollo-server/package.json | 4 +- packages/apollo-server/src/index.ts | 2 - packages/apollo-tracing/package.json | 4 +- packages/graphql-extensions/package.json | 4 +- renovate.json5 | 4 - types/loglevel-debug/index.d.ts | 4 + 63 files changed, 485 insertions(+), 1931 deletions(-) delete mode 100644 packages/apollo-gateway/src/__tests__/integration/logger.test.ts delete mode 100644 packages/apollo-gateway/src/__tests__/loadServicesFromRemoteEndpoint.test.ts delete mode 100644 packages/apollo-server-core/src/__tests__/logger.test.ts create mode 100644 types/loglevel-debug/index.d.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index 7f55e8944ff..bddd2fccefb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -37,10 +37,10 @@ jobs: # at https://hub.docker.com/r/circleci/node/. # NODE: Note certain tests are currently being skipped for Node.js 6. - # NodeJS 6: - # executor: { name: oss/node, tag: '6' } - # steps: - # - common_test_steps + NodeJS 6: + executor: { name: oss/node, tag: '6' } + steps: + - common_test_steps NodeJS 8: executor: { name: oss/node, tag: '8' } @@ -92,8 +92,8 @@ workflows: version: 2 Build: jobs: - # - NodeJS 6: - # <<: *common_non_publish_filters + - NodeJS 6: + <<: *common_non_publish_filters - NodeJS 8: <<: *common_non_publish_filters - NodeJS 10: @@ -104,7 +104,7 @@ workflows: name: Package tarballs <<: *common_non_publish_filters requires: - # - NodeJS 6 + - NodeJS 6 - NodeJS 8 - NodeJS 10 - NodeJS 12 @@ -112,7 +112,7 @@ workflows: name: Dry-run <<: *common_publish_filters requires: - # - NodeJS 6 + - NodeJS 6 - NodeJS 8 - NodeJS 10 - NodeJS 12 diff --git a/CHANGELOG.md b/CHANGELOG.md index 100d21c435b..721a11bd183 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ The version headers in this history reflect the versions of Apollo Server itself - _Nothing yet! Stay tuned._ +### v2.12.1 + +- Revert everything from the `release-2.12.0` merge commit + ### v2.12.0 > [See complete versioning details.](https://github.com/apollographql/apollo-server/commit/71a3863f59f4ab2c9052c316479d94c6708c4309) diff --git a/docs/source/api/apollo-gateway.mdx b/docs/source/api/apollo-gateway.mdx index 7f2e3701d36..4bc25991967 100644 --- a/docs/source/api/apollo-gateway.mdx +++ b/docs/source/api/apollo-gateway.mdx @@ -99,19 +99,13 @@ example of using `ApolloGateway`, see [The gateway](/federation/gateway/). }); ``` - * `logger`: `Logger` - - A logging implementation to be used in place of `console`. The implementation must provide the methods which satisfy the requirements of [the `Logger` interface](https://github.com/apollographql/apollo-server/blob/80a12d89ea1ae9a0892f4a81d9213eddf95ca965/packages/apollo-server-types/src/index.ts#L114-L121) (i.e. it must provide `debug`, `info`, `warn` and `error` methods). When a custom logger is provided, it will receive all levels of logging and it is up to the logger itself to determine how it wishes to handle each level. When a custom logger is _not_ provided, Gateway will default to outputting `warn` and `error` levels unless `debug: true` is specified, in which case it will output all log levels (i.e. `debug` through `error`). - - Additionally, this `logger` will be made available on the `GraphQLRequestContext` and available to plugins. This allows a plugin to, e.g., augment the logger on a per-request basis within the `requestDidStart` life-cycle hook. - * `debug`: `Boolean` If `true`, the gateway logs startup messages, along with the query plan for each incoming request. The default value is `false`. - + * `fetcher`: `typeof fetch` - + When specified, overrides the default [Fetch API](https://fetch.spec.whatwg.org/#fetch-api) implementation which is used when communicating with downstream services. By default, @@ -121,7 +115,7 @@ example of using `ApolloGateway`, see [The gateway](/federation/gateway/). `fetcher: require('node-fetch')`) or different [default options for `make-fetch-happen`](https://www.npmjs.com/package/make-fetch-happen#extra-options) can be defined entirely. E.g.: - + ```javascript const gateway = new ApolloGateway({ /* ... */ @@ -134,14 +128,6 @@ example of using `ApolloGateway`, see [The gateway](/federation/gateway/). }); ``` - * `serviceHealthCheck`: `boolean` - - When set to true, the gateway will issue a small query (`{ __typename }`) to - its downstream services on load and on schema update. On load, the gateway - will throw an error if any of the requests fail. On schema update, the - gateway will not roll over to the new schema or service configuration if any - of the requests fail, but it will try again at the next poll interval. - * `experimental_approximateQueryPlanStoreMiB`: `number` > **This property is experimental.** It may be removed or change at any time, even within a patch release. @@ -164,20 +150,6 @@ const server = new ApolloServer({ }); ``` -### `serviceHealthCheck` - -Calling this function on the gateway will issue a small query (`{ __typename }`) -to each downstream service to ensure they are all responsive. This function -`throw`s on failure and returns a `Promise` to be `await`ed. - -#### Parameters - -* `serviceMap`: `DataSourceMap` - - A map of data sources can optionally be provided to this function in order to - modify where the health checks will be issued. By default, the gateway will - use the existing `this.serviceMap` for issuing the health check requests. - ## `RemoteGraphQLDataSource` A `RemoteGraphQLDataSource` object represents a connection between your diff --git a/docs/source/api/apollo-server.md b/docs/source/api/apollo-server.md index c5ba9fc92a6..4dac689d772 100644 --- a/docs/source/api/apollo-server.md +++ b/docs/source/api/apollo-server.md @@ -68,12 +68,6 @@ new ApolloServer({ | AWS Lambda | {
  event: [`APIGatewayProxyEvent`](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/50adc95acf873e714256074311353232fcc1b5ed/types/aws-lambda/index.d.ts#L78-L92),
  context: [`LambdaContext`](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/50adc95acf873e714256074311353232fcc1b5ed/types/aws-lambda/index.d.ts#L510-L534)
}
| | Micro | { req: [`MicroRequest`](https://github.com/apollographql/apollo-server/blob/c356bcf3f2864b8d2fcca0add455071e0606ef46/packages/apollo-server-micro/src/types.ts#L3-L5), res: [`ServerResponse`](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/50adc95acf873e714256074311353232fcc1b5ed/types/node/v10/http.d.ts#L145-L158) } | - * `logger`: `Logger` - - A logging implementation to be used in place of `console`. The implementation must provide the methods which satisfy the requirements of [the `Logger` interface](https://github.com/apollographql/apollo-server/blob/80a12d89ea1ae9a0892f4a81d9213eddf95ca965/packages/apollo-server-types/src/index.ts#L114-L121) (i.e. it must provide `debug`, `info`, `warn` and `error` methods). When a custom logger is provided, it will receive all levels of logging and it is up to the logger itself to determine how it wishes to handle each level. When a custom logger is _not_ provided, Apollo Server will default to outputting `warn` and `error` levels unless `debug: true` is specified, in which case it will output all log levels (i.e. `debug` through `error`). - - Additionally, this `logger` will be made available on the `GraphQLRequestContext` and available to plugins. This allows a plugin to, e.g., augment the logger on a per-request basis within the `requestDidStart` life-cycle hook. - * `rootValue`: <`Any`> | <`Function`> A value or function called with the parsed `Document`, creating the root value passed to the graphql executor. The function is useful if you wish to provide a different root value based on the query operation type. @@ -343,10 +337,6 @@ addMockFunctionsToSchema({ a service. You can also specify an API key with the `ENGINE_API_KEY` environment variable, although the `apiKey` option takes precedence. -* `logger`: `Logger` - - By default, this will inherit from the `logger` provided to `ApolloServer` which defaults to `console` when not provided. If specified within the `EngineReportingOptions` it can be used to send engine reporting to a separate logger. If provided, the implementation must provide the methods which satisfy the requirements of [the `Logger` interface](https://github.com/apollographql/apollo-server/blob/80a12d89ea1ae9a0892f4a81d9213eddf95ca965/packages/apollo-server-types/src/index.ts#L114-L121) (i.e. it must provide `debug`, `info`, `warn` and `error` methods). - * `calculateSignature`: (ast: DocumentNode, operationName: string) => string Specify the function for creating a signature for a query. diff --git a/docs/source/integrations/plugins.md b/docs/source/integrations/plugins.md index 2ab21962324..0400c487195 100644 --- a/docs/source/integrations/plugins.md +++ b/docs/source/integrations/plugins.md @@ -269,15 +269,6 @@ const server = new ApolloServer({ The `requestDidStart` event fires whenever Apollo Server begins fulfilling a GraphQL request. -```typescript -requestDidStart?( - requestContext: WithRequired< - GraphQLRequestContext, - 'request' | 'context' | 'logger' - > -): GraphQLRequestListener | void; -``` - This function can optionally return an object that includes functions for responding to request lifecycle events that might follow `requestDidStart`. @@ -327,7 +318,7 @@ does not occur. parsingDidStart?( requestContext: WithRequired< GraphQLRequestContext, - 'metrics' | 'source' | 'logger' + 'metrics' | 'source' >, ): (err?: Error) => void | void; ``` @@ -347,7 +338,7 @@ available at this stage, because parsing must succeed for validation to occur. validationDidStart?( requestContext: WithRequired< GraphQLRequestContext, - 'metrics' | 'source' | 'document' | 'logger' + 'metrics' | 'source' | 'document' >, ): (err?: ReadonlyArray) => void | void; ``` @@ -364,7 +355,7 @@ both the `operationName` string and `operation` AST are available. didResolveOperation?( requestContext: WithRequired< GraphQLRequestContext, - 'metrics' | 'source' | 'document' | 'operationName' | 'operation' | 'logger' + 'metrics' | 'source' | 'document' | 'operationName' | 'operation' >, ): ValueOrPromise; ``` @@ -380,7 +371,7 @@ are invoked in series, and the first non-null response is used. responseForOperation?( requestContext: WithRequired< GraphQLRequestContext, - 'metrics' | 'source' | 'document' | 'operationName' | 'operation' | 'logger' + 'metrics' | 'source' | 'document' | 'operationName' | 'operation' >, ): ValueOrPromise; ``` @@ -394,7 +385,7 @@ GraphQL operation specified by a request's `document` AST. executionDidStart?( requestContext: WithRequired< GraphQLRequestContext, - 'metrics' | 'source' | 'document' | 'operationName' | 'operation' | 'logger' + 'metrics' | 'source' | 'document' | 'operationName' | 'operation' >, ): (err?: Error) => void | void; ``` @@ -408,7 +399,7 @@ parsing, validating, or executing a GraphQL operation. didEncounterErrors?( requestContext: WithRequired< GraphQLRequestContext, - 'metrics' | 'source' | 'errors' | 'logger' + 'metrics' | 'source' | 'errors' >, ): ValueOrPromise; ``` @@ -423,7 +414,7 @@ if the GraphQL operation encounters one or more errors. willSendResponse?( requestContext: WithRequired< GraphQLRequestContext, - 'metrics' | 'response' | 'logger' + 'metrics' | 'response' >, ): ValueOrPromise; ``` diff --git a/package-lock.json b/package-lock.json index e833d18a7f2..98befbb2f40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "apollo-server-types": "file:packages/apollo-server-types", "graphql-extensions": "file:packages/graphql-extensions", "loglevel": "^1.6.1", + "loglevel-debug": "^0.0.1", "make-fetch-happen": "^7.1.1", "pretty-format": "^24.7.0" }, @@ -3591,15 +3592,6 @@ "integrity": "sha512-PH7bfkt1nu4pnlxz+Ws+wwJJF1HE12W3ia+Iace2JT7q56DLH3hbyjOJyNHJYRxk3PkKaC36fHfHKyeG1rMgCA==", "dev": true }, - "@types/bunyan": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-1.8.6.tgz", - "integrity": "sha512-YiozPOOsS6bIuz31ilYqR5SlLif4TBWsousN2aCWLi5233nZSX19tFbcQUPdR7xJ8ypPyxkCGNxg0CIV5n9qxQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/caseless": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", @@ -4463,17 +4455,9 @@ "graphql-tag": "^2.9.2", "graphql-tools": "^4.0.0", "graphql-upload": "^8.0.2", - "loglevel": "^1.6.7", "sha.js": "^2.4.11", "subscriptions-transport-ws": "^0.9.11", "ws": "^6.0.0" - }, - "dependencies": { - "loglevel": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz", - "integrity": "sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==" - } } }, "apollo-server-env": { @@ -4821,15 +4805,6 @@ "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", @@ -5239,18 +5214,6 @@ "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", "dev": true }, - "bunyan": { - "version": "1.8.12", - "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz", - "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=", - "dev": true, - "requires": { - "dtrace-provider": "~0.8", - "moment": "^2.10.6", - "mv": "~2", - "safe-json-stringify": "~1" - } - }, "busboy": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz", @@ -5619,16 +5582,6 @@ "object-visit": "^1.0.0" } }, - "color": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", - "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", - "dev": true, - "requires": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" - } - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -5642,38 +5595,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, - "color-string": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", - "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", - "dev": true, - "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "colornames": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", - "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=", - "dev": true - }, - "colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true - }, - "colorspace": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", - "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", - "dev": true, - "requires": { - "color": "3.0.x", - "text-hex": "1.0.x" - } - }, "columnify": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz", @@ -6361,11 +6282,10 @@ } } }, - "date-format": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", - "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", - "dev": true + "dataloader": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz", + "integrity": "sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==" }, "dateformat": { "version": "3.0.3", @@ -6585,17 +6505,6 @@ "wrappy": "1" } }, - "diagnostics": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", - "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", - "dev": true, - "requires": { - "colorspace": "1.1.x", - "enabled": "1.0.x", - "kuler": "1.0.x" - } - }, "dicer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", @@ -6637,25 +6546,6 @@ "is-obj": "^1.0.0" } }, - "dtrace-provider": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", - "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", - "dev": true, - "optional": true, - "requires": { - "nan": "^2.14.0" - }, - "dependencies": { - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "dev": true, - "optional": true - } - } - }, "duplexer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", @@ -6727,15 +6617,6 @@ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, - "enabled": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", - "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", - "dev": true, - "requires": { - "env-variable": "0.0.x" - } - }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -6764,12 +6645,6 @@ "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==", "dev": true }, - "env-variable": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.6.tgz", - "integrity": "sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg==", - "dev": true - }, "envinfo": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.5.0.tgz", @@ -7434,12 +7309,6 @@ "bser": "^2.0.0" } }, - "fecha": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", - "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==", - "dev": true - }, "figgy-pudding": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", @@ -7524,12 +7393,6 @@ "integrity": "sha512-qFlJnOBWDfIaunF54/lBqNKmXOI0HqNhu+mHkLmbaBXlS71PUd9OjFOdyevHt/aHoHB1+eW7eKHgRKOG5aHSpw==", "dev": true }, - "flatted": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", - "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", - "dev": true - }, "flush-write-stream": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", @@ -12027,15 +11890,6 @@ } } }, - "kuler": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", - "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", - "dev": true, - "requires": { - "colornames": "^1.1.1" - } - }, "left-pad": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", @@ -12203,74 +12057,19 @@ "resolved": "https://registry.npmjs.org/lodash.xorby/-/lodash.xorby-4.7.0.tgz", "integrity": "sha1-nBmm+fBjputT3QPBtocXmYAUY9c=" }, - "log4js": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.5.1.tgz", - "integrity": "sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==", - "dev": true, - "requires": { - "date-format": "^2.0.0", - "debug": "^4.1.1", - "flatted": "^2.0.0", - "rfdc": "^1.1.4", - "streamroller": "^1.0.6" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "rfdc": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz", - "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==", - "dev": true - } - } - }, - "logform": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz", - "integrity": "sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ==", - "dev": true, - "requires": { - "colors": "^1.2.1", - "fast-safe-stringify": "^2.0.4", - "fecha": "^2.3.3", - "ms": "^2.1.1", - "triple-beam": "^1.3.0" - }, - "dependencies": { - "fast-safe-stringify": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", - "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, "loglevel": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.4.tgz", "integrity": "sha512-p0b6mOGKcGa+7nnmKbpzR6qloPbrgLcnio++E+14Vo/XffOGwZtRpUhr8dTH/x2oCMmEoIU0Zwm3ZauhvYD17g==" }, + "loglevel-debug": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/loglevel-debug/-/loglevel-debug-0.0.1.tgz", + "integrity": "sha1-ifidPboTy6iiy0YV1dOtcYn0iik=", + "requires": { + "loglevel": "^1.4.0" + } + }, "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", @@ -12945,13 +12744,6 @@ "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", "dev": true }, - "moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", - "dev": true, - "optional": true - }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -12988,44 +12780,6 @@ "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", "dev": true }, - "mv": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", - "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", - "dev": true, - "optional": true, - "requires": { - "mkdirp": "~0.5.1", - "ncp": "~2.0.0", - "rimraf": "~2.4.0" - }, - "dependencies": { - "glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", - "dev": true, - "optional": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "rimraf": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", - "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", - "dev": true, - "optional": true, - "requires": { - "glob": "^6.0.1" - } - } - } - }, "mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -13069,13 +12823,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "ncp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", - "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", - "dev": true, - "optional": true - }, "needle": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", @@ -13526,12 +13273,6 @@ "wrappy": "1" } }, - "one-time": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz", - "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=", - "dev": true - }, "onetime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", @@ -14566,13 +14307,6 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, - "safe-json-stringify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", - "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", - "dev": true, - "optional": true - }, "safe-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", @@ -14755,23 +14489,6 @@ "resolved": "https://registry.npmjs.org/simple-lru-cache/-/simple-lru-cache-0.0.2.tgz", "integrity": "sha1-1ZzDoZPBpdAyD4Tucy9uRxPlEd0=" }, - "simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", - "dev": true, - "requires": { - "is-arrayish": "^0.3.1" - }, - "dependencies": { - "is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "dev": true - } - } - }, "sisteransi": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.3.tgz", @@ -15061,12 +14778,6 @@ "figgy-pudding": "^3.5.1" } }, - "stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", - "dev": true - }, "stack-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", @@ -15135,47 +14846,6 @@ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, - "streamroller": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.6.tgz", - "integrity": "sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==", - "dev": true, - "requires": { - "async": "^2.6.2", - "date-format": "^2.0.0", - "debug": "^3.2.6", - "fs-extra": "^7.0.1", - "lodash": "^4.17.14" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, "streamsearch": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", @@ -15530,12 +15200,6 @@ "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", "dev": true }, - "text-hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", - "dev": true - }, "thenify": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", @@ -15719,12 +15383,6 @@ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", "dev": true }, - "triple-beam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", - "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==", - "dev": true - }, "ts-invariant": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.4.4.tgz", @@ -16167,65 +15825,6 @@ "execa": "^1.0.0" } }, - "winston": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", - "integrity": "sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw==", - "dev": true, - "requires": { - "async": "^2.6.1", - "diagnostics": "^1.1.1", - "is-stream": "^1.1.0", - "logform": "^2.1.1", - "one-time": "0.0.4", - "readable-stream": "^3.1.1", - "stack-trace": "0.0.x", - "triple-beam": "^1.3.0", - "winston-transport": "^4.3.0" - } - }, - "winston-transport": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.3.0.tgz", - "integrity": "sha512-B2wPuwUi3vhzn/51Uukcao4dIduEiPOcOt9HJ3QeaXgkJ5Z7UwpBzxS4ZGNHtrxrUvTwemsQiSys0ihOf8Mp1A==", - "dev": true, - "requires": { - "readable-stream": "^2.3.6", - "triple-beam": "^1.2.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", diff --git a/package.json b/package.json index 909d144fd5b..451468ba88d 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,8 @@ "compile:clean": "tsc --build tsconfig.build.json --clean", "watch": "tsc --build tsconfig.build.json --watch", "release:version-bump": "lerna version -m 'Release'", - "release:version-bump:server": "npm run release:version-bump -- --force-publish=apollo-server,apollo-server-core,apollo-server-azure-functions,apollo-server-cloud-functions,apollo-server-cloudflare,apollo-server-express,apollo-server-fastify,apollo-server-hapi,apollo-server-koa,apollo-server-lambda,apollo-server-micro,apollo-server-integration-testsuite,apollo-server-testing", + "release:version-bump:server": "npm run release:version-bump -- --force-publish=apollo-server,apollo-server-core,apollo-server-azure-functions,apollo-server-cloud-functions,apollo-server-cloudflare,apollo-server-express,apollo-server-fastify,apollo-server-hapi,apollo-server-koa,apollo-server-lambda,apollo-server-micro", "release:version-bump:federation": "npm run release:version-bump -- --force-publish=@apollo/federation,@apollo/gateway", - "release:version-bump:server-and-federation": "npm run release:version-bump -- --force-publish=@apollo/federation,@apollo/gateway,apollo-server,apollo-server-core,apollo-server-azure-functions,apollo-server-cloud-functions,apollo-server-cloudflare,apollo-server-express,apollo-server-fastify,apollo-server-hapi,apollo-server-koa,apollo-server-lambda,apollo-server-micro,apollo-server-integration-testsuite,apollo-server-testing", "release:start-ci-publish": "node -p '`Publish (dist-tag:${process.env.APOLLO_DIST_TAG || \"latest\"})`' | git tag -F - \"publish/$(date -u '+%Y%m%d%H%M%S')\" && git push origin \"$(git describe --match='publish/*' --tags --exact-match HEAD)\"", "postinstall": "lerna run prepare && npm run compile", "test": "jest --verbose", @@ -62,7 +61,6 @@ "@types/async-retry": "1.4.1", "@types/aws-lambda": "8.10.48", "@types/body-parser": "1.19.0", - "@types/bunyan": "1.8.6", "@types/connect": "3.4.33", "@types/fast-json-stable-stringify": "2.0.0", "@types/hapi": "17.8.7", @@ -91,7 +89,6 @@ "apollo-link-persisted-queries": "0.2.2", "azure-functions-ts-essentials": "1.3.2", "body-parser": "1.19.0", - "bunyan": "1.8.12", "codecov": "3.6.5", "connect": "3.7.0", "deep-freeze": "0.0.1", @@ -111,7 +108,6 @@ "js-sha256": "0.9.0", "koa": "2.11.0", "lerna": "3.20.2", - "log4js": "4.5.1", "memcached-mock": "0.1.0", "mock-req": "0.2.0", "nock": "10.0.6", @@ -125,8 +121,6 @@ "test-listen": "1.1.0", "ts-jest": "24.3.0", "typescript": "3.8.3", - "winston": "3.2.1", - "winston-transport": "4.3.0", "ws": "6.2.1" }, "jest": { diff --git a/packages/apollo-cache-control/package.json b/packages/apollo-cache-control/package.json index 95a50d0fde3..4fbb9715371 100644 --- a/packages/apollo-cache-control/package.json +++ b/packages/apollo-cache-control/package.json @@ -1,6 +1,6 @@ { "name": "apollo-cache-control", - "version": "0.9.1", + "version": "0.9.0", "description": "A GraphQL extension for cache control", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -15,6 +15,6 @@ "graphql-extensions": "file:../graphql-extensions" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-datasource-rest/package.json b/packages/apollo-datasource-rest/package.json index 3a50b7f4c3b..da8a4f68237 100644 --- a/packages/apollo-datasource-rest/package.json +++ b/packages/apollo-datasource-rest/package.json @@ -1,6 +1,6 @@ { "name": "apollo-datasource-rest", - "version": "0.8.1", + "version": "0.8.0", "author": "opensource@apollographql.com", "license": "MIT", "repository": { diff --git a/packages/apollo-engine-reporting/package.json b/packages/apollo-engine-reporting/package.json index f8dfc3f24a1..45b1eb2580f 100644 --- a/packages/apollo-engine-reporting/package.json +++ b/packages/apollo-engine-reporting/package.json @@ -1,6 +1,6 @@ { "name": "apollo-engine-reporting", - "version": "1.7.1", + "version": "1.7.0", "description": "Send reports about your GraphQL services to Apollo Graph Manager (previously known as Apollo Engine)", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -21,6 +21,6 @@ "graphql-extensions": "file:../graphql-extensions" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-engine-reporting/src/agent.ts b/packages/apollo-engine-reporting/src/agent.ts index cab2ec9d982..3b1df8ac6b5 100644 --- a/packages/apollo-engine-reporting/src/agent.ts +++ b/packages/apollo-engine-reporting/src/agent.ts @@ -12,7 +12,7 @@ import { fetch, RequestAgent, Response } from 'apollo-server-env'; import retry from 'async-retry'; import { EngineReportingExtension } from './extension'; -import { GraphQLRequestContext, Logger } from 'apollo-server-types'; +import { GraphQLRequestContext } from 'apollo-server-types'; import { InMemoryLRUCache } from 'apollo-server-caching'; import { defaultEngineReportingSignature } from 'apollo-graphql'; @@ -189,13 +189,6 @@ export interface EngineReportingOptions { * Creates the client information for operation traces. */ generateClientInfo?: GenerateClientInfo; - - /** - * A logger interface to be used for output and errors. When not provided - * it will default to the server's own `logger` implementation and use - * `console` when that is not available. - */ - logger?: Logger; } export interface AddTraceArgs { @@ -220,7 +213,6 @@ const serviceHeaderDefaults = { // to the Engine server. export class EngineReportingAgent { private options: EngineReportingOptions; - private logger: Logger = console; private apiKey: string; private reports: { [schemaHash: string]: FullTracesReport } = Object.create( null, @@ -238,7 +230,6 @@ export class EngineReportingAgent { public constructor(options: EngineReportingOptions = {}) { this.options = options; - if (options.logger) this.logger = options.logger; this.apiKey = options.apiKey || process.env.ENGINE_API_KEY || ''; if (!this.apiKey) { throw new Error( @@ -249,7 +240,7 @@ export class EngineReportingAgent { // Since calculating the signature for Engine reporting is potentially an // expensive operation, we'll cache the signatures we generate and re-use // them based on repeated traces for the same `queryHash`. - this.signatureCache = createSignatureCache({ logger: this.logger }); + this.signatureCache = createSignatureCache(); this.sendReportsImmediately = options.sendReportsImmediately; if (!this.sendReportsImmediately) { @@ -370,17 +361,7 @@ export class EngineReportingAgent { await Promise.resolve(); if (this.options.debugPrintReports) { - // In terms of verbosity, and as the name of this option suggests, this - // message is either an "info" or a "debug" level message. However, - // we are using `warn` here for compatibility reasons since the - // `debugPrintReports` flag pre-dated the existence of log-levels and - // changing this to also require `debug: true` (in addition to - // `debugPrintReports`) just to reach the level of verbosity to produce - // the output would be a breaking change. The "warn" level is on by - // default. There is a similar theory and comment applied below. - this.logger.warn( - `Engine sending report: ${JSON.stringify(report.toJSON())}`, - ); + console.log(`Engine sending report: ${JSON.stringify(report.toJSON())}`); } const protobufError = FullTracesReport.verify(report); @@ -457,15 +438,7 @@ export class EngineReportingAgent { ); } if (this.options.debugPrintReports) { - // In terms of verbosity, and as the name of this option suggests, this - // message is either an "info" or a "debug" level message. However, - // we are using `warn` here for compatibility reasons since the - // `debugPrintReports` flag pre-dated the existence of log-levels and - // changing this to also require `debug: true` (in addition to - // `debugPrintReports`) just to reach the level of verbosity to produce - // the output would be a breaking change. The "warn" level is on by - // default. There is a similar theory and comment applied above. - this.logger.warn(`Engine report: status ${response.status}`); + console.log(`Engine report: status ${response.status}`); } } @@ -551,7 +524,7 @@ export class EngineReportingAgent { if (this.options.reportErrorFunction) { this.options.reportErrorFunction(err); } else { - this.logger.error(err.message); + console.error(err.message); } }); } @@ -564,11 +537,7 @@ export class EngineReportingAgent { } } -function createSignatureCache({ - logger, -}: { - logger: Logger; -}): InMemoryLRUCache { +function createSignatureCache(): InMemoryLRUCache { let lastSignatureCacheWarn: Date; let lastSignatureCacheDisposals: number = 0; return new InMemoryLRUCache({ @@ -597,7 +566,7 @@ function createSignatureCache({ ) { // Log the time that we last displayed the message. lastSignatureCacheWarn = new Date(); - logger.warn( + console.warn( [ 'This server is processing a high number of unique operations. ', `A total of ${lastSignatureCacheDisposals} records have been `, diff --git a/packages/apollo-engine-reporting/src/extension.ts b/packages/apollo-engine-reporting/src/extension.ts index a9199e88fd4..0fd804df7f7 100644 --- a/packages/apollo-engine-reporting/src/extension.ts +++ b/packages/apollo-engine-reporting/src/extension.ts @@ -1,4 +1,4 @@ -import { GraphQLRequestContext, WithRequired, Logger } from 'apollo-server-types'; +import { GraphQLRequestContext, WithRequired } from 'apollo-server-types'; import { Request, Headers } from 'apollo-server-env'; import { GraphQLResolveInfo, @@ -30,7 +30,6 @@ const clientVersionHeaderKey = 'apollographql-client-version'; // Its public methods all implement the GraphQLExtension interface. export class EngineReportingExtension implements GraphQLExtension { - private logger: Logger = console; private treeBuilder: EngineReportingTreeBuilder; private explicitOperationName?: string | null; private queryString?: string; @@ -47,14 +46,12 @@ export class EngineReportingExtension this.options = { ...options, }; - if (options.logger) this.logger = options.logger; this.addTrace = addTrace; this.generateClientInfo = options.generateClientInfo || defaultGenerateClientInfo; this.treeBuilder = new EngineReportingTreeBuilder({ rewriteError: options.rewriteError, - logger: this.logger, }); } diff --git a/packages/apollo-engine-reporting/src/treeBuilder.ts b/packages/apollo-engine-reporting/src/treeBuilder.ts index a10535bbe23..448c180f81c 100644 --- a/packages/apollo-engine-reporting/src/treeBuilder.ts +++ b/packages/apollo-engine-reporting/src/treeBuilder.ts @@ -1,10 +1,15 @@ -import { GraphQLError, GraphQLResolveInfo, ResponsePath } from 'graphql'; +import { + GraphQLResolveInfo, + GraphQLError, + ResponsePath, + responsePathAsArray, +} from 'graphql'; import { Trace, google } from 'apollo-engine-reporting-protobuf'; import { PersistedQueryNotFoundError, PersistedQueryNotSupportedError, } from 'apollo-server-errors'; -import { InvalidGraphQLRequestError, Logger } from 'apollo-server-types'; +import { InvalidGraphQLRequestError } from 'apollo-server-types'; function internalError(message: string) { return new Error(`[internal apollo-server error] ${message}`); @@ -12,21 +17,18 @@ function internalError(message: string) { export class EngineReportingTreeBuilder { private rootNode = new Trace.Node(); - private logger: Logger = console; public trace = new Trace({ root: this.rootNode }); public startHrTime?: [number, number]; private stopped = false; private nodes = new Map([ - [responsePathAsString(), this.rootNode], + [rootResponsePath, this.rootNode], ]); private rewriteError?: (err: GraphQLError) => GraphQLError | null; public constructor(options: { - logger?: Logger; rewriteError?: (err: GraphQLError) => GraphQLError | null; }) { this.rewriteError = options.rewriteError; - if (options.logger) this.logger = options.logger; } public startTiming() { @@ -135,7 +137,7 @@ export class EngineReportingTreeBuilder { if (specificNode) { node = specificNode; } else { - this.logger.warn( + console.warn( `Could not find node with path ${path.join( '.', )}; defaulting to put errors on root node.`, @@ -244,22 +246,15 @@ function durationHrTimeToNanos(hrtime: [number, number]) { // Convert from the linked-list ResponsePath format to a dot-joined // string. Includes the full path (field names and array indices). -function responsePathAsString(p?: ResponsePath): string { +function responsePathAsString(p: ResponsePath | undefined) { if (p === undefined) { return ''; } - - // A previous implementation used `responsePathAsArray` from `graphql-js/execution`, - // however, that employed an approach that created new arrays unnecessarily. - let res = String(p.key); - - while ((p = p.prev) !== undefined) { - res = `${p.key}.${res}`; - } - - return res; + return responsePathAsArray(p).join('.'); } +const rootResponsePath = responsePathAsString(undefined); + function errorToProtobufError(error: GraphQLError): Trace.Error { return new Trace.Error({ message: error.message, diff --git a/packages/apollo-federation/CHANGELOG.md b/packages/apollo-federation/CHANGELOG.md index bb287a57477..cadd522e433 100644 --- a/packages/apollo-federation/CHANGELOG.md +++ b/packages/apollo-federation/CHANGELOG.md @@ -6,6 +6,10 @@ - _Nothing yet! Stay tuned._ +## 0.14.1 + +- Revert everything from the `release-2.12.0` merge commit + ## 0.14.0 > [See complete versioning details.](https://github.com/apollographql/apollo-server/commit/71a3863f59f4ab2c9052c316479d94c6708c4309) diff --git a/packages/apollo-federation/package.json b/packages/apollo-federation/package.json index 0004853940d..5e0f8c8e2eb 100644 --- a/packages/apollo-federation/package.json +++ b/packages/apollo-federation/package.json @@ -1,6 +1,6 @@ { "name": "@apollo/federation", - "version": "0.14.0", + "version": "0.13.2", "description": "Apollo Federation Utilities", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -20,6 +20,6 @@ "lodash.xorby": "^4.7.0" }, "peerDependencies": { - "graphql": "^14.0.2 || ^15.0.0" + "graphql": "^14.0.2 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-gateway/CHANGELOG.md b/packages/apollo-gateway/CHANGELOG.md index 63abdd2466a..b9c79cd6df9 100644 --- a/packages/apollo-gateway/CHANGELOG.md +++ b/packages/apollo-gateway/CHANGELOG.md @@ -6,6 +6,10 @@ - _Nothing yet! Stay tuned._ +## 0.14.1 + +- Revert everything from the `release-2.12.0` merge commit + ## 0.14.0 > [See complete versioning details.](https://github.com/apollographql/apollo-server/commit/71a3863f59f4ab2c9052c316479d94c6708c4309) @@ -18,8 +22,6 @@ - Support providing a custom logger implementation (e.g. [`winston`](https://npm.im/winston), [`bunyan`](https://npm.im/bunyan), etc.) to capture gateway-sourced console output. This allows the use of existing, production logging facilities or the possibiltiy to use advanced structure in logging, such as console output which is encapsulated in JSON. The same PR that introduces this support also introduces a `logger` property to the `GraphQLRequestContext` that is exposed to `GraphQLDataSource`s and Apollo Server plugins, making it possible to attach additional properties (as supported by the logger implementation) to specific requests, if desired, by leveraging custom implementations in those components respectively. When not provided, these will still output to `console`. [PR #3894](https://github.com/apollographql/apollo-server/pull/3894) - Drop use of `loglevel-debug`. This removes the very long date and time prefix in front of each log line and also the support for the `DEBUG=apollo-gateway:` environment variable. Both of these were uncommonly necessary or seldom used (with the environment variable also being undocumented). The existing behavior can be preserved by providing a `logger` that uses `loglevel-debug`, if desired, and more details can be found in the PR. [PR #3896](https://github.com/apollographql/apollo-server/pull/3896) - Fix Typescript generic typing for datasource contexts [#3865](https://github.com/apollographql/apollo-server/pull/3865) This is a fix for the `TContext` typings of the gateway's exposed `GraphQLDataSource` implementations. In their current form, they don't work as intended, or in any manner that's useful for typing the `context` property throughout the class methods. This introduces a type argument `TContext` to the class itself (which defaults to `Record` for existing implementations) and removes the non-operational type arguments on the class methods themselves. -- Implement retry logic for requests to GCS [PR #3836](https://github.com/apollographql/apollo-server/pull/3836) Note: coupled with this change is a small alteration in how the gateway polls GCS for updates in managed mode. Previously, the tick was on a specific interval. Now, every tick starts after the round of fetches to GCS completes. For more details, see the linked PR. -- Gateway issues health checks to downstream services via `serviceHealthCheck` configuration option. Note: expected behavior differs between managed and unmanaged federation. See PR for new test cases and documentation. [#3930](https://github.com/apollographql/apollo-server/pull/3930) ## 0.13.2 diff --git a/packages/apollo-gateway/package.json b/packages/apollo-gateway/package.json index 8d338126307..afb50536e78 100644 --- a/packages/apollo-gateway/package.json +++ b/packages/apollo-gateway/package.json @@ -1,6 +1,6 @@ { "name": "@apollo/gateway", - "version": "0.14.0", + "version": "0.13.2", "description": "Apollo Gateway", "author": "opensource@apollographql.com", "main": "dist/index.js", @@ -30,10 +30,11 @@ "apollo-server-types": "file:../apollo-server-types", "graphql-extensions": "file:../graphql-extensions", "loglevel": "^1.6.1", + "loglevel-debug": "^0.0.1", "make-fetch-happen": "^7.1.1", "pretty-format": "^24.7.0" }, "peerDependencies": { - "graphql": "^14.2.1 || ^15.0.0" + "graphql": "^14.2.1 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-gateway/src/__tests__/gateway/buildService.test.ts b/packages/apollo-gateway/src/__tests__/gateway/buildService.test.ts index 3361f7ad4b0..564d101962b 100644 --- a/packages/apollo-gateway/src/__tests__/gateway/buildService.test.ts +++ b/packages/apollo-gateway/src/__tests__/gateway/buildService.test.ts @@ -4,7 +4,7 @@ import { createTestClient } from 'apollo-server-testing'; import { ApolloServerBase as ApolloServer } from 'apollo-server-core'; import { RemoteGraphQLDataSource } from '../../datasources/RemoteGraphQLDataSource'; -import { ApolloGateway, SERVICE_DEFINITION_QUERY } from '../../'; +import { ApolloGateway } from '../../'; import * as accounts from '../__fixtures__/schemas/accounts'; import * as books from '../__fixtures__/schemas/books'; @@ -133,7 +133,7 @@ it('makes enhanced introspection request using datasource', async () => { expect(fetch).toHaveFetched({ url: 'https://api.example.com/override', body: { - query: SERVICE_DEFINITION_QUERY, + query: `query GetServiceDefinition { _service { sdl } }`, }, headers: { 'custom-header': 'some-custom-value', @@ -179,7 +179,7 @@ it('customizes request on a per-service basis', async () => { expect(fetch).toHaveFetched({ url: 'https://api.example.com/one', body: { - query: `query __ApolloGetServiceDefinition__ { _service { sdl } }`, + query: `query GetServiceDefinition { _service { sdl } }`, }, headers: { 'service-name': 'one', @@ -189,7 +189,7 @@ it('customizes request on a per-service basis', async () => { expect(fetch).toHaveFetched({ url: 'https://api.example.com/two', body: { - query: `query __ApolloGetServiceDefinition__ { _service { sdl } }`, + query: `query GetServiceDefinition { _service { sdl } }`, }, headers: { 'service-name': 'two', @@ -199,7 +199,7 @@ it('customizes request on a per-service basis', async () => { expect(fetch).toHaveFetched({ url: 'https://api.example.com/three', body: { - query: `query __ApolloGetServiceDefinition__ { _service { sdl } }`, + query: `query GetServiceDefinition { _service { sdl } }`, }, headers: { 'service-name': 'three', diff --git a/packages/apollo-gateway/src/__tests__/gateway/executor.test.ts b/packages/apollo-gateway/src/__tests__/gateway/executor.test.ts index 7e8a7490f64..9d73387ab14 100644 --- a/packages/apollo-gateway/src/__tests__/gateway/executor.test.ts +++ b/packages/apollo-gateway/src/__tests__/gateway/executor.test.ts @@ -6,7 +6,6 @@ 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 () => { @@ -36,24 +35,4 @@ 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 267c15cc984..3b3f31dd692 100644 --- a/packages/apollo-gateway/src/__tests__/gateway/lifecycle-hooks.test.ts +++ b/packages/apollo-gateway/src/__tests__/gateway/lifecycle-hooks.test.ts @@ -56,7 +56,9 @@ describe('lifecycle hooks', () => { experimental_didFailComposition, }); - await expect(gateway.load()).rejects.toThrowError(); + try { + await gateway.load(); + } catch {} const callbackArgs = experimental_didFailComposition.mock.calls[0][0]; expect(callbackArgs.serviceList).toHaveLength(1); diff --git a/packages/apollo-gateway/src/__tests__/integration/logger.test.ts b/packages/apollo-gateway/src/__tests__/integration/logger.test.ts deleted file mode 100644 index 690c046d421..00000000000 --- a/packages/apollo-gateway/src/__tests__/integration/logger.test.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { ApolloGateway } from '../..'; -import { Logger } from "apollo-server-types"; -import { PassThrough } from "stream"; - -import * as winston from "winston"; -import WinstonTransport from 'winston-transport'; -import * as bunyan from "bunyan"; -import * as loglevel from "loglevel"; -// We are testing an older version of `log4js` which uses older ECMAScript -// in order to still support testing on Node.js 6. -// This should be updated when bump the semver major for AS3. -import * as log4js from "log4js"; - -const LOWEST_LOG_LEVEL = "debug"; - -const KNOWN_DEBUG_MESSAGE = "Checking service definitions..."; - -async function triggerKnownDebugMessage(logger: Logger) { - // Trigger a known error. - // This is a bit brittle since it merely leverages a known debug log - // message outside of the constructor, but it seemed worth testing - // the compatibility with `ApolloGateway` itself rather than generically. - // The error does not matter, so it is caught and ignored. - await new ApolloGateway({ logger }).load().catch(_e => {}); -} - -describe("logger", () => { - it("works with 'winston'", async () => { - const sink = jest.fn(); - const transport = new class extends WinstonTransport { - constructor() { - super({ - format: winston.format.json(), - }); - } - - log(info: any) { - sink(info); - } - }; - - const logger = winston.createLogger({ level: 'debug' }).add(transport); - - await triggerKnownDebugMessage(logger); - - expect(sink).toHaveBeenCalledWith(expect.objectContaining({ - level: LOWEST_LOG_LEVEL, - message: KNOWN_DEBUG_MESSAGE, - })); - }); - - it("works with 'bunyan'", async () => { - const sink = jest.fn(); - - // Bunyan uses streams for its logging implementations. - const writable = new PassThrough(); - writable.on("data", data => sink(JSON.parse(data.toString()))); - - const logger = bunyan.createLogger({ - name: "test-logger-bunyan", - streams: [{ - level: LOWEST_LOG_LEVEL, - stream: writable, - }] - }); - - await triggerKnownDebugMessage(logger); - - expect(sink).toHaveBeenCalledWith(expect.objectContaining({ - level: bunyan.DEBUG, - msg: KNOWN_DEBUG_MESSAGE, - })); - }); - - it("works with 'loglevel'", async () => { - const sink = jest.fn(); - - const logger = loglevel.getLogger("test-logger-loglevel") - logger.methodFactory = (_methodName, level): loglevel.LoggingMethod => - (message) => sink({ level, message }); - - // The `setLevel` method must be called after overwriting `methodFactory`. - // This is an intentional API design pattern of the loglevel package: - // https://www.npmjs.com/package/loglevel#writing-plugins - logger.setLevel(loglevel.levels.DEBUG); - - await triggerKnownDebugMessage(logger); - - expect(sink).toHaveBeenCalledWith({ - level: loglevel.levels.DEBUG, - message: KNOWN_DEBUG_MESSAGE, - }); - }); - - it("works with 'log4js'", async () => { - const sink = jest.fn(); - - log4js.configure({ - appenders: { - custom: { - type: { - configure: () => - (loggingEvent: log4js.LoggingEvent) => sink(loggingEvent) - } - } - }, - categories: { - default: { - appenders: ['custom'], - level: LOWEST_LOG_LEVEL, - } - } - }); - - const logger = log4js.getLogger(); - logger.level = LOWEST_LOG_LEVEL; - - await triggerKnownDebugMessage(logger); - - expect(sink).toHaveBeenCalledWith(expect.objectContaining({ - level: log4js.levels.DEBUG, - data: [KNOWN_DEBUG_MESSAGE], - })); - }); -}); diff --git a/packages/apollo-gateway/src/__tests__/integration/networkRequests.test.ts b/packages/apollo-gateway/src/__tests__/integration/networkRequests.test.ts index df976f8029a..ddd11d60b61 100644 --- a/packages/apollo-gateway/src/__tests__/integration/networkRequests.test.ts +++ b/packages/apollo-gateway/src/__tests__/integration/networkRequests.test.ts @@ -1,43 +1,27 @@ import nock from 'nock'; -import { fetch } from 'apollo-server-env'; -import { Logger } from 'apollo-server-types'; -import { ApolloGateway, GCS_RETRY_COUNT, getDefaultGcsFetcher } from '../..'; +import { ApolloGateway } from '../..'; import { - mockSDLQuerySuccess, - mockServiceHealthCheckSuccess, - mockServiceHealthCheck, + mockLocalhostSDLQuery, mockStorageSecretSuccess, - mockStorageSecret, mockCompositionConfigLinkSuccess, - mockCompositionConfigLink, mockCompositionConfigsSuccess, - mockCompositionConfigs, mockImplementingServicesSuccess, - mockImplementingServices, mockRawPartialSchemaSuccess, - mockRawPartialSchema, apiKeyHash, graphId, + mockImplementingServices, + mockRawPartialSchema, } from './nockMocks'; -import loadServicesFromStorage = require("../../loadServicesFromStorage"); - // This is a nice DX hack for GraphQL code highlighting and formatting within the file. // Anything wrapped within the gql tag within this file is just a string, not an AST. const gql = String.raw; -export interface MockService { - gcsDefinitionPath: string; - partialSchemaPath: string; - url: string; - sdl: string; -} - -const service: MockService = { - gcsDefinitionPath: 'service-definition.json', +const service = { + implementingServicePath: 'service-definition.json', partialSchemaPath: 'accounts-partial-schema.json', - url: 'http://localhost:4001', - sdl: gql` + federatedServiceURL: 'http://localhost:4001', + federatedServiceSchema: gql` extend type Query { me: User everyone: [User] @@ -49,14 +33,14 @@ const service: MockService = { name: String username: String } - `, + ` }; -const updatedService: MockService = { - gcsDefinitionPath: 'updated-service-definition.json', +const updatedService = { + implementingServicePath: 'updated-service-definition.json', partialSchemaPath: 'updated-accounts-partial-schema.json', - url: 'http://localhost:4002', - sdl: gql` + federatedServiceURL: 'http://localhost:4002', + federatedServiceSchema: gql` extend type Query { me: User everyone: [User] @@ -68,374 +52,94 @@ const updatedService: MockService = { name: String username: String } - `, -}; - -let fetcher: typeof fetch; -let logger: Logger; + ` +} beforeEach(() => { if (!nock.isActive()) nock.activate(); - - fetcher = getDefaultGcsFetcher().defaults({ - retry: { - retries: GCS_RETRY_COUNT, - minTimeout: 0, - maxTimeout: 0, - }, - }); - - const warn = jest.fn(); - const debug = jest.fn(); - const error = jest.fn(); - const info = jest.fn(); - - logger = { - warn, - debug, - error, - info, - }; }); afterEach(() => { expect(nock.isDone()).toBeTruthy(); nock.cleanAll(); nock.restore(); + jest.useRealTimers(); }); it('Queries remote endpoints for their SDLs', async () => { - mockSDLQuerySuccess(service); + mockLocalhostSDLQuery({ url: service.federatedServiceURL }).reply(200, { + data: { _service: { sdl: service.federatedServiceSchema } }, + }); const gateway = new ApolloGateway({ - serviceList: [{ name: 'accounts', url: service.url }], - logger + serviceList: [ + { name: 'accounts', url: `${service.federatedServiceURL}/graphql` }, + ], }); await gateway.load(); expect(gateway.schema!.getType('User')!.description).toBe('This is my User'); }); +// This test is maybe a bit terrible, but IDK a better way to mock all the requests it('Extracts service definitions from remote storage', async () => { mockStorageSecretSuccess(); mockCompositionConfigLinkSuccess(); - mockCompositionConfigsSuccess([service]); + mockCompositionConfigsSuccess([service.implementingServicePath]); mockImplementingServicesSuccess(service); mockRawPartialSchemaSuccess(service); - const gateway = new ApolloGateway({ logger }); + const gateway = new ApolloGateway({}); await gateway.load({ engine: { apiKeyHash, graphId } }); expect(gateway.schema!.getType('User')!.description).toBe('This is my User'); }); -it.each([ - ['warned', 'present'], - ['not warned', 'absent'], -])('conflicting configurations are %s about when %s', async (_word, mode) => { - const isConflict = mode === 'present'; - let blockerResolve: () => void; - const blocker = new Promise(resolve => (blockerResolve = resolve)); - const original = loadServicesFromStorage.getServiceDefinitionsFromStorage; - const spyGetServiceDefinitionsFromStorage = jest - .spyOn(loadServicesFromStorage, 'getServiceDefinitionsFromStorage') - .mockImplementationOnce(async (...args) => { - try { - return await original(...args); - } catch (e) { - throw e; - } finally { - setImmediate(blockerResolve); - } - }); - - mockStorageSecretSuccess(); - if (isConflict) { - mockCompositionConfigLinkSuccess(); - mockCompositionConfigsSuccess([service]); - mockImplementingServicesSuccess(service); - mockRawPartialSchemaSuccess(service); - } else { - mockCompositionConfigLink().reply(403); - } - - mockSDLQuerySuccess(service); - - const gateway = new ApolloGateway({ - serviceList: [ - { name: 'accounts', url: service.url }, - ], - logger - }); - - await gateway.load({ engine: { apiKeyHash, graphId } }); - await blocker; // Wait for the definitions to be "fetched". - - (isConflict - ? expect(logger.warn) - : expect(logger.warn).not - ).toHaveBeenCalledWith(expect.stringMatching( - /A local gateway service list is overriding an Apollo Graph Manager managed configuration/)); - spyGetServiceDefinitionsFromStorage.mockRestore(); -}); - it('Rollsback to a previous schema when triggered', async () => { // Init mockStorageSecretSuccess(); mockCompositionConfigLinkSuccess(); - mockCompositionConfigsSuccess([service]); + mockCompositionConfigsSuccess([service.implementingServicePath]); mockImplementingServicesSuccess(service); mockRawPartialSchemaSuccess(service); // Update 1 mockStorageSecretSuccess(); mockCompositionConfigLinkSuccess(); - mockCompositionConfigsSuccess([updatedService]); + mockCompositionConfigsSuccess([updatedService.implementingServicePath]); mockImplementingServicesSuccess(updatedService); mockRawPartialSchemaSuccess(updatedService); // Rollback mockStorageSecretSuccess(); mockCompositionConfigLinkSuccess(); - mockCompositionConfigsSuccess([service]); + mockCompositionConfigsSuccess([service.implementingServicePath]); mockImplementingServices(service).reply(304); mockRawPartialSchema(service).reply(304); - let firstResolve: () => void; - let secondResolve: () => void; - const firstSchemaChangeBlocker = new Promise(res => (firstResolve = res)); - const secondSchemaChangeBlocker = new Promise(res => (secondResolve = res)); - - const onChange = jest - .fn() - .mockImplementationOnce(() => firstResolve()) - .mockImplementationOnce(() => secondResolve()); - - const gateway = new ApolloGateway({ logger }); - // @ts-ignore for testing purposes, a short pollInterval is ideal so we'll override here - gateway.experimental_pollInterval = 100; + jest.useFakeTimers(); + const onChange = jest.fn(); + const gateway = new ApolloGateway(); await gateway.load({ engine: { apiKeyHash, graphId } }); gateway.onSchemaChange(onChange); - await firstSchemaChangeBlocker; - expect(onChange.mock.calls.length).toBe(1); - - await secondSchemaChangeBlocker; - expect(onChange.mock.calls.length).toBe(2); -}); - -function failNTimes(n: number, fn: () => nock.Interceptor) { - for (let i = 0; i < n; i++) { - fn().reply(500); - } -} - -it(`Retries GCS (up to ${GCS_RETRY_COUNT} times) on failure for each request and succeeds`, async () => { - failNTimes(GCS_RETRY_COUNT, mockStorageSecret); - mockStorageSecretSuccess(); - - failNTimes(GCS_RETRY_COUNT, mockCompositionConfigLink); - mockCompositionConfigLinkSuccess(); - - failNTimes(GCS_RETRY_COUNT, mockCompositionConfigs); - mockCompositionConfigsSuccess([service]); - - failNTimes(GCS_RETRY_COUNT, () => mockImplementingServices(service)); - mockImplementingServicesSuccess(service); - - failNTimes(GCS_RETRY_COUNT, () => mockRawPartialSchema(service)); - mockRawPartialSchemaSuccess(service); - - const gateway = new ApolloGateway({ fetcher, logger }); - - await gateway.load({ engine: { apiKeyHash, graphId } }); - expect(gateway.schema!.getType('User')!.description).toBe('This is my User'); -}); - -it(`Fails after the ${GCS_RETRY_COUNT + 1}th attempt to reach GCS`, async () => { - failNTimes(GCS_RETRY_COUNT + 1, mockStorageSecret); - - const gateway = new ApolloGateway({ fetcher, logger }); - await expect( - gateway.load({ engine: { apiKeyHash, graphId } }), - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Could not communicate with Apollo Graph Manager storage: "`, - ); -}); - -it(`Errors when the secret isn't hosted on GCS`, async () => { - mockStorageSecret().reply( - 403, - `AccessDenied - Anonymous caller does not have storage.objects.get`, - { 'content-type': 'application/xml' }, - ); - - const gateway = new ApolloGateway({ fetcher, logger }); - await expect( - gateway.load({ engine: { apiKeyHash, graphId } }), - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Unable to authenticate with Apollo Graph Manager storage while fetching https://storage.googleapis.com/engine-partial-schema-prod/federated-service/storage-secret/dd55a79d467976346d229a7b12b673ce.json. 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."`, - ); -}); - -describe('Downstream service health checks', () => { - describe('Unmanaged mode', () => { - it(`Performs health checks to downstream services on load`, async () => { - mockSDLQuerySuccess(service); - mockServiceHealthCheckSuccess(service); - - const gateway = new ApolloGateway({ - logger, - serviceList: [{ name: 'accounts', url: service.url }], - serviceHealthCheck: true, - }); - - await gateway.load(); - expect(gateway.schema!.getType('User')!.description).toBe('This is my User'); - }); - - it(`Rejects on initial load when health check fails`, async () => { - mockSDLQuerySuccess(service); - mockServiceHealthCheck(service).reply(500); - - const gateway = new ApolloGateway({ - serviceList: [{ name: 'accounts', url: service.url }], - serviceHealthCheck: true, - logger, - }); - - await expect(gateway.load()).rejects.toThrowErrorMatchingInlineSnapshot( - `"500: Internal Server Error"`, - ); - }); - }); - - describe('Managed mode', () => { - it('Performs health checks to downstream services on load', async () => { - mockStorageSecretSuccess(); - mockCompositionConfigLinkSuccess(); - mockCompositionConfigsSuccess([service]); - mockImplementingServicesSuccess(service); - mockRawPartialSchemaSuccess(service); + // 10000 ms is the default pollInterval + jest.advanceTimersByTime(10000); - mockServiceHealthCheckSuccess(service); + // This useReal/useFake is challenging to explain the need for, and I probably + // don't have the _correct_ answer here, but it seems that pushing this process + // to the back of the task queue is insufficient. + jest.useRealTimers(); + await new Promise(resolve => setTimeout(resolve, 100)); + jest.useFakeTimers(); - const gateway = new ApolloGateway({ serviceHealthCheck: true, logger }); - - await gateway.load({ engine: { apiKeyHash, graphId } }); - expect(gateway.schema!.getType('User')!.description).toBe('This is my User'); - }); - - it('Rejects on initial load when health check fails', async () => { - mockStorageSecretSuccess(); - mockCompositionConfigLinkSuccess(); - mockCompositionConfigsSuccess([service]); - mockImplementingServicesSuccess(service); - mockRawPartialSchemaSuccess(service); - - mockServiceHealthCheck(service).reply(500); - - const gateway = new ApolloGateway({ serviceHealthCheck: true, logger }); - - await expect( - gateway.load({ engine: { apiKeyHash, graphId } }), - ).rejects.toThrowErrorMatchingInlineSnapshot(`"500: Internal Server Error"`); - }); - - it('Rolls over to new schema when health check succeeds', async () => { - mockStorageSecretSuccess(); - mockCompositionConfigLinkSuccess(); - mockCompositionConfigsSuccess([service]); - mockImplementingServicesSuccess(service); - mockRawPartialSchemaSuccess(service); - mockServiceHealthCheckSuccess(service); - - // Update - mockStorageSecretSuccess(); - mockCompositionConfigLinkSuccess(); - mockCompositionConfigsSuccess([updatedService]); - mockImplementingServicesSuccess(updatedService); - mockRawPartialSchemaSuccess(updatedService); - mockServiceHealthCheckSuccess(updatedService); - - let resolve: () => void; - const schemaChangeBlocker = new Promise(res => (resolve = res)); - const onChange = jest.fn().mockImplementationOnce(() => resolve()); - - const gateway = new ApolloGateway({ - serviceHealthCheck: true, - logger, - }); - // @ts-ignore for testing purposes, a short pollInterval is ideal so we'll override here - gateway.experimental_pollInterval = 100; - - await gateway.load({ engine: { apiKeyHash, graphId } }); - gateway.onSchemaChange(onChange); - - await schemaChangeBlocker; - expect(onChange.mock.calls.length).toBe(1); - - expect(gateway.schema!.getType('User')!.description).toBe('This is my updated User'); - }); - - it('Preserves original schema when health check fails', async () => { - mockStorageSecretSuccess(); - mockCompositionConfigLinkSuccess(); - mockCompositionConfigsSuccess([service]); - mockImplementingServicesSuccess(service); - mockRawPartialSchemaSuccess(service); - mockServiceHealthCheckSuccess(service); - - // Update - mockStorageSecretSuccess(); - mockCompositionConfigLinkSuccess(); - mockCompositionConfigsSuccess([updatedService]); - mockImplementingServicesSuccess(updatedService); - mockRawPartialSchemaSuccess(updatedService); - mockServiceHealthCheck(updatedService).reply(500); - - let resolve: () => void; - const schemaChangeBlocker = new Promise(res => (resolve = res)); - - const gateway = new ApolloGateway({ serviceHealthCheck: true, logger }); - // @ts-ignore for testing purposes, a short pollInterval is ideal so we'll override here - gateway.experimental_pollInterval = 100; - - // load the gateway as usual - await gateway.load({ engine: { apiKeyHash, graphId } }); - expect(gateway.schema!.getType('User')!.description).toBe('This is my User'); - - // @ts-ignore for testing purposes, we'll call the original `updateComposition` - // function from our mock - const original = gateway.updateComposition; - const mockUpdateComposition = jest - .fn(original) - .mockImplementationOnce(async opts => { - // mock the first poll and handle the error which would otherwise be caught - // and logged from within the `pollServices` class method - await expect(original.apply(gateway, [opts])) - .rejects - .toThrowErrorMatchingInlineSnapshot( - `"500: Internal Server Error"`, - ); - // finally resolve the promise which drives this test - resolve(); - }); - - // @ts-ignore for testing purposes, replace the `updateComposition` - // function on the gateway with our mock - gateway.updateComposition = mockUpdateComposition; + expect(onChange.mock.calls.length).toBe(1); - // This kicks off polling within the gateway - gateway.onSchemaChange(() => {}); + jest.advanceTimersByTime(10000); - await schemaChangeBlocker; + jest.useRealTimers(); + await new Promise(resolve => setTimeout(resolve, 100)); + jest.useFakeTimers(); - // At this point, the mock update should have been called but the schema - // should not have updated to the new one. - expect(mockUpdateComposition.mock.calls.length).toBe(1); - expect(gateway.schema!.getType('User')!.description).toBe('This is my User'); - }); - }); + expect(onChange.mock.calls.length).toBe(2); }); diff --git a/packages/apollo-gateway/src/__tests__/integration/nockMocks.ts b/packages/apollo-gateway/src/__tests__/integration/nockMocks.ts index 7a14eb1139c..8368fbf00a2 100644 --- a/packages/apollo-gateway/src/__tests__/integration/nockMocks.ts +++ b/packages/apollo-gateway/src/__tests__/integration/nockMocks.ts @@ -1,38 +1,5 @@ import nock from 'nock'; -import { HEALTH_CHECK_QUERY, SERVICE_DEFINITION_QUERY } from '../..'; -import { MockService } from './networkRequests.test'; -export const graphId = 'federated-service'; -export const apiKeyHash = 'dd55a79d467976346d229a7b12b673ce'; -const storageSecret = 'my-storage-secret'; -const accountsService = 'accounts'; - -// Service mocks -function mockSDLQuery({ url }: MockService) { - return nock(url).post('/', { - query: SERVICE_DEFINITION_QUERY, - }); -} - -export function mockSDLQuerySuccess(service: MockService) { - mockSDLQuery(service).reply(200, { - data: { _service: { sdl: service.sdl } }, - }); -} - -export function mockServiceHealthCheck({ url }: MockService) { - return nock(url).post('/', { - query: HEALTH_CHECK_QUERY, - }); -} - -export function mockServiceHealthCheckSuccess(service: MockService) { - return mockServiceHealthCheck(service).reply(200, { - data: { __typename: 'Query' }, - }); -} - -// GCS mocks function gcsNock(url: Parameters[0]): nock.Scope { return nock(url, { reqheaders: { @@ -43,71 +10,100 @@ function gcsNock(url: Parameters[0]): nock.Scope { }); } -export function mockStorageSecret() { - return gcsNock('https://storage.googleapis.com:443').get( +export const mockLocalhostSDLQuery = ({ url }: { url: string }) => + nock(url).post('/graphql', { + query: 'query GetServiceDefinition { _service { sdl } }', + }); + +export const graphId = 'federated-service'; +export const apiKeyHash = 'dd55a79d467976346d229a7b12b673ce'; +const storageSecret = 'my-storage-secret'; +const accountsService = 'accounts'; + +export const mockStorageSecret = () => + gcsNock('https://storage.googleapis.com:443').get( `/engine-partial-schema-prod/${graphId}/storage-secret/${apiKeyHash}.json`, ); -} -export function mockStorageSecretSuccess() { - return gcsNock('https://storage.googleapis.com:443') +export const mockStorageSecretSuccess = () => + gcsNock('https://storage.googleapis.com:443') .get( `/engine-partial-schema-prod/${graphId}/storage-secret/${apiKeyHash}.json`, ) .reply(200, `"${storageSecret}"`); -} // get composition config link, using received storage secret -export function mockCompositionConfigLink() { - return gcsNock('https://storage.googleapis.com:443').get( +export const mockCompositionConfigLink = () => + gcsNock('https://storage.googleapis.com:443').get( `/engine-partial-schema-prod/${storageSecret}/current/v1/composition-config-link`, ); -} -export function mockCompositionConfigLinkSuccess() { - return mockCompositionConfigLink().reply(200, { +export const mockCompositionConfigLinkSuccess = () => + mockCompositionConfigLink().reply(200, { configPath: `${storageSecret}/current/v1/composition-configs/composition-config-path.json`, }); -} // get composition configs, using received composition config link -export function mockCompositionConfigs() { - return gcsNock('https://storage.googleapis.com:443').get( +export const mockCompositionConfigs = () => + gcsNock('https://storage.googleapis.com:443').get( `/engine-partial-schema-prod/${storageSecret}/current/v1/composition-configs/composition-config-path.json`, ); -} -export function mockCompositionConfigsSuccess(services: MockService[]) { - return mockCompositionConfigs().reply(200, { - implementingServiceLocations: services.map(service => ({ +export const mockCompositionConfigsSuccess = ( + implementingServicePaths: string[], +) => + mockCompositionConfigs().reply(200, { + implementingServiceLocations: implementingServicePaths.map(servicePath => ({ name: accountsService, - path: `${storageSecret}/current/v1/implementing-services/${accountsService}/${service.gcsDefinitionPath}`, + path: `${storageSecret}/current/v1/implementing-services/${accountsService}/${servicePath}`, })), }); -} // get implementing service reference, using received composition-config -export function mockImplementingServices({ gcsDefinitionPath }: MockService) { - return gcsNock('https://storage.googleapis.com:443').get( - `/engine-partial-schema-prod/${storageSecret}/current/v1/implementing-services/${accountsService}/${gcsDefinitionPath}`, +export const mockImplementingServices = ({ + implementingServicePath, +}: { + implementingServicePath: string; +}) => + gcsNock('https://storage.googleapis.com:443').get( + `/engine-partial-schema-prod/${storageSecret}/current/v1/implementing-services/${accountsService}/${implementingServicePath}`, ); -} -export function mockImplementingServicesSuccess(service: MockService) { - return mockImplementingServices(service).reply(200, { +export const mockImplementingServicesSuccess = ({ + implementingServicePath, + partialSchemaPath, + federatedServiceURL, +}: { + implementingServicePath: string; + partialSchemaPath: string; + federatedServiceURL: string; +}) => + mockImplementingServices({ + implementingServicePath, + }).reply(200, { name: accountsService, - partialSchemaPath: `${storageSecret}/current/raw-partial-schemas/${service.partialSchemaPath}`, - url: service.url, + partialSchemaPath: `${storageSecret}/current/raw-partial-schemas/${partialSchemaPath}`, + url: federatedServiceURL, }); -} // get raw-partial-schema, using received composition-config -export function mockRawPartialSchema({ partialSchemaPath }: MockService) { - return gcsNock('https://storage.googleapis.com:443').get( +export const mockRawPartialSchema = ({ + partialSchemaPath, +}: { + partialSchemaPath: string; +}) => + gcsNock('https://storage.googleapis.com:443').get( `/engine-partial-schema-prod/${storageSecret}/current/raw-partial-schemas/${partialSchemaPath}`, ); -} -export function mockRawPartialSchemaSuccess(service: MockService) { - mockRawPartialSchema(service).reply(200, service.sdl); -} +export const mockRawPartialSchemaSuccess = ({ + partialSchemaPath, + federatedServiceSchema, +}: { + partialSchemaPath: string; + federatedServiceSchema: string; +}) => + mockRawPartialSchema({ partialSchemaPath }).reply( + 200, + federatedServiceSchema, + ); diff --git a/packages/apollo-gateway/src/__tests__/loadServicesFromRemoteEndpoint.test.ts b/packages/apollo-gateway/src/__tests__/loadServicesFromRemoteEndpoint.test.ts deleted file mode 100644 index 2adf87141cc..00000000000 --- a/packages/apollo-gateway/src/__tests__/loadServicesFromRemoteEndpoint.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { getServiceDefinitionsFromRemoteEndpoint } from '../loadServicesFromRemoteEndpoint'; -import { mockLocalhostSDLQuery } from './integration/nockMocks'; -import { RemoteGraphQLDataSource } from '../datasources'; -import nock = require('nock'); - -describe('getServiceDefinitionsFromRemoteEndpoint', () => { - it('errors when no URL was specified', async () => { - const serviceSdlCache = new Map(); - const dataSource = new RemoteGraphQLDataSource({ url: '' }); - const serviceList = [{ name: 'test', dataSource }]; - await expect( - getServiceDefinitionsFromRemoteEndpoint({ - serviceList, - serviceSdlCache, - }), - ).rejects.toThrowError( - "Tried to load schema for 'test' but no 'url' was specified.", - ); - }); - - it('throws when the downstream service returns errors', async () => { - const serviceSdlCache = new Map(); - const host = 'http://host-which-better-not-resolve'; - const url = host + '/graphql'; - - const dataSource = new RemoteGraphQLDataSource({ url }); - const serviceList = [{ name: 'test', url, dataSource }]; - await expect( - getServiceDefinitionsFromRemoteEndpoint({ - serviceList, - serviceSdlCache, - }), - ).rejects.toThrowError(/^Couldn't load service definitions for "test" at http:\/\/host-which-better-not-resolve\/graphql: request to http:\/\/host-which-better-not-resolve\/graphql failed, reason: getaddrinfo ENOTFOUND/); - }); -}); diff --git a/packages/apollo-gateway/src/executeQueryPlan.ts b/packages/apollo-gateway/src/executeQueryPlan.ts index 0527065f252..6e68cc68830 100644 --- a/packages/apollo-gateway/src/executeQueryPlan.ts +++ b/packages/apollo-gateway/src/executeQueryPlan.ts @@ -93,7 +93,7 @@ export async function executeQueryPlan( }, rootValue: data, variableValues: requestContext.request.variables, - // FIXME: GraphQL extensions currently wraps every field and creates + // FIXME: GraphQL extensions currentl wraps every field and creates // a field resolver. Because of this, when using with ApolloServer // the defaultFieldResolver isn't called. We keep this here // because it is the correct solution and when ApolloServer removes @@ -202,7 +202,6 @@ async function executeFetch( _path: ResponsePath, traceNode: Trace.QueryPlanNode.FetchNode | null, ): Promise { - const logger = context.requestContext.logger || console; const service = context.serviceMap[fetch.serviceName]; if (!service) { throw new Error(`Couldn't find service with name "${fetch.serviceName}"`); @@ -352,7 +351,7 @@ async function executeFetch( // supports that, but there's not a no-deps base64 implementation. traceBuffer = Buffer.from(traceBase64, 'base64'); } catch (err) { - logger.error( + console.error( `error decoding base64 for federated trace from ${fetch.serviceName}: ${err}`, ); traceParsingFailed = true; @@ -363,7 +362,7 @@ async function executeFetch( const trace = Trace.decode(traceBuffer); traceNode.trace = trace; } catch (err) { - logger.error( + console.error( `error decoding protobuf for federated trace from ${fetch.serviceName}: ${err}`, ); traceParsingFailed = true; diff --git a/packages/apollo-gateway/src/index.ts b/packages/apollo-gateway/src/index.ts index fa736d34a6f..a34db0a362d 100644 --- a/packages/apollo-gateway/src/index.ts +++ b/packages/apollo-gateway/src/index.ts @@ -6,8 +6,8 @@ import { } from 'apollo-server-core'; import { GraphQLExecutionResult, - Logger, - GraphQLRequestContextExecutionDidStart, + GraphQLRequestContext, + WithRequired, } from 'apollo-server-types'; import { InMemoryLRUCache } from 'apollo-server-caching'; import { @@ -19,7 +19,8 @@ import { } from 'graphql'; import { GraphQLSchemaValidationError } from 'apollo-graphql'; import { composeAndValidate, ServiceDefinition } from '@apollo/federation'; -import loglevel from 'loglevel'; +import loglevel, { Logger } from 'loglevel'; +import loglevelDebug from 'loglevel-debug'; import { buildQueryPlan, buildOperationContext } from './buildQueryPlan'; import { @@ -47,7 +48,6 @@ export type ServiceEndpointDefinition = Pick; interface GatewayConfigBase { debug?: boolean; - logger?: Logger; // TODO: expose the query plan in a more flexible JSON format in the future // and remove this config option in favor of `exposeQueryPlan`. Playground // should cutover to use the new option when it's built. @@ -63,7 +63,6 @@ interface GatewayConfigBase { experimental_approximateQueryPlanStoreMiB?: number; experimental_autoFragmentization?: boolean; fetcher?: typeof fetch; - serviceHealthCheck?: boolean; } interface RemoteGatewayConfig extends GatewayConfigBase { @@ -83,7 +82,7 @@ export type GatewayConfig = | LocalGatewayConfig | ManagedGatewayConfig; -type DataSourceMap = { +type DataSourceCache = { [serviceName: string]: { url?: string; dataSource: GraphQLDataSource }; }; @@ -148,47 +147,16 @@ export type Experimental_UpdateServiceDefinitions = ( type Await = T extends Promise ? U : T; -// Local state to track whether particular UX-improving warning messages have -// already been emitted. This is particularly useful to prevent recurring -// warnings of the same type in, e.g. repeating timers, which don't provide -// additional value when they are repeated over and over during the life-time -// of a server. -type WarnedStates = { - remoteWithLocalConfig?: boolean; -}; - -export const GCS_RETRY_COUNT = 5; - -export function getDefaultGcsFetcher() { - return fetcher.defaults({ - cacheManager: new HttpRequestCache(), - // All headers should be lower-cased here, as `make-fetch-happen` - // treats differently cased headers as unique (unlike the `Headers` object). - // @see: https://git.io/JvRUa - headers: { - 'user-agent': `apollo-gateway/${require('../package.json').version}`, - }, - retry: { - retries: GCS_RETRY_COUNT, - // The default factor: expected attempts at 0, 1, 3, 7, 15, and 31 seconds elapsed - factor: 2, - // 1 second - minTimeout: 1000, - randomize: true, - }, - }); -} - -export const HEALTH_CHECK_QUERY = - 'query __ApolloServiceHealthCheck__ { __typename }'; -export const SERVICE_DEFINITION_QUERY = - 'query __ApolloGetServiceDefinition__ { _service { sdl } }'; +type RequestContext = WithRequired< + GraphQLRequestContext, + 'document' | 'queryHash' +>; export class ApolloGateway implements GraphQLService { public schema?: GraphQLSchema; - protected serviceMap: DataSourceMap = Object.create(null); + protected serviceMap: DataSourceCache = Object.create(null); protected config: GatewayConfig; - private logger: Logger; + protected logger: Logger; protected queryPlanStore?: InMemoryLRUCache; private engineConfig: GraphQLServiceEngineConfig | undefined; private pollingTimer?: NodeJS.Timer; @@ -196,9 +164,16 @@ export class ApolloGateway implements GraphQLService { private serviceDefinitions: ServiceDefinition[] = []; private compositionMetadata?: CompositionMetadata; private serviceSdlCache = new Map(); - private warnedStates: WarnedStates = Object.create(null); - private fetcher: typeof fetch = getDefaultGcsFetcher(); + private fetcher: typeof fetch = fetcher.defaults({ + cacheManager: new HttpRequestCache(), + // All headers should be lower-cased here, as `make-fetch-happen` + // treats differently cased headers as unique (unlike the `Headers` object). + // @see: https://git.io/JvRUa + headers: { + 'user-agent': `apollo-gateway/${require('../package.json').version}` + } + }); // Observe query plan, service info, and operation info prior to execution. // The information made available here will give insight into the resulting @@ -228,21 +203,15 @@ export class ApolloGateway implements GraphQLService { ...config, }; - // Setup logging facilities - if (this.config.logger) { - this.logger = this.config.logger; - } else { - // If the user didn't provide their own logger, we'll initialize one. - const loglevelLogger = loglevel.getLogger(`apollo-gateway`); + // Setup logging facilities, scoped under the appropriate name. + this.logger = loglevel.getLogger(`apollo-gateway:`); - // And also support the `debug` option, if it's truthy. - if (this.config.debug === true) { - loglevelLogger.setLevel(loglevelLogger.levels.DEBUG); - } else { - loglevelLogger.setLevel(loglevelLogger.levels.WARN); - } + // Support DEBUG environment variable, à la https://npm.im/debug/. + loglevelDebug(this.logger); - this.logger = loglevelLogger; + // And also support the `debug` option, if it's truthy. + if (this.config.debug === true) { + this.logger.enableAll(); } if (isLocalConfig(this.config)) { @@ -282,7 +251,7 @@ export class ApolloGateway implements GraphQLService { this.experimental_pollInterval = config.experimental_pollInterval; } - // Warn against using the pollInterval and a serviceList simultaneously + // Warn against using the pollInterval and a serviceList simulatenously if (config.experimental_pollInterval && isRemoteConfig(config)) { this.logger.warn( 'Polling running services is dangerous and not recommended in production. ' + @@ -319,7 +288,7 @@ export class ApolloGateway implements GraphQLService { protected async updateComposition(options?: { engine?: GraphQLServiceEngineConfig; }): Promise { - // The options argument and internal config update could be handled by this.load() + // The options argument and internal config update coule be handled by this.load() // instead of here. We can remove this as a breaking change in the future. if (options && options.engine) { if (!options.engine.graphVariant) @@ -327,16 +296,20 @@ 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('Checking service definitions...'); + this.logger.debug('Loading configuration for gateway'); try { result = await this.updateServiceDefinitions(this.config); } catch (e) { - this.logger.error( - "Error checking for changes to service definitions: " + - (e && e.message || e) + this.logger.warn( + 'Error checking for schema updates. Falling back to existing schema.', + e, ); - throw e; + return; } if ( @@ -344,43 +317,12 @@ 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("New service definitions were found."); - } - - // Run service health checks before we commit and update the new schema. - // This is the last chance to bail out of a schema update. - if (this.config.serviceHealthCheck) { - // Here we need to construct new datasources based on the new schema info - // so we can check the health of the services we're _updating to_. - const serviceMap = result.serviceDefinitions.reduce( - (serviceMap, serviceDef) => { - serviceMap[serviceDef.name] = { - url: serviceDef.url, - dataSource: this.createDataSource(serviceDef), - }; - return serviceMap; - }, - Object.create(null) as DataSourceMap, - ); - - try { - await this.serviceHealthCheck(serviceMap); - } catch (e) { - this.logger.error( - 'The gateway did not update its schema due to failed service health checks. ' + - 'The gateway will continue to operate with the previous schema and reattempt updates.' + e - ); - throw e; - } + this.logger.info('Gateway config has changed, updating schema'); } this.compositionMetadata = result.compositionMetadata; @@ -389,14 +331,13 @@ export class ApolloGateway implements GraphQLService { if (this.queryPlanStore) this.queryPlanStore.flush(); this.schema = this.createSchema(result.serviceDefinitions); - - // Notify the schema listeners of the updated schema try { this.onSchemaChangeListeners.forEach(listener => listener(this.schema!)); } catch (e) { this.logger.error( - "An error was thrown from an 'onSchemaChange' listener. " + - "The schema will still update: " + (e && e.message || e)); + 'Error notifying schema change listener of update to schema.', + e, + ); } if (this.experimental_didUpdateComposition) { @@ -420,31 +361,6 @@ export class ApolloGateway implements GraphQLService { } } - /** - * This can be used without an argument in order to perform an ad-hoc health check - * of the downstream services like so: - * - * @example - * ``` - * try { - * await gateway.serviceHealthCheck(); - * } catch(e) { - * /* your error handling here *\/ - * } - * ``` - * @throws - * @param serviceMap {DataSourceMap} - */ - public serviceHealthCheck(serviceMap: DataSourceMap = this.serviceMap) { - return Promise.all( - Object.entries(serviceMap).map(([name, { dataSource }]) => - dataSource - .process({ request: { query: HEALTH_CHECK_QUERY }, context: {} }) - .then(response => ({ name, response })), - ), - ); - } - protected createSchema(serviceList: ServiceDefinition[]) { this.logger.debug( `Composing schema from service list: \n${serviceList @@ -474,8 +390,8 @@ export class ApolloGateway implements GraphQLService { // this is a temporary workaround for GraphQLFieldExtensions automatic // wrapping of all fields when using ApolloServer. Here we wrap all fields // with support for resolving aliases as part of the root value which - // happens because aliases are resolved by sub services and the shape - // of the root value already contains the aliased fields as responseNames + // happens because alises are resolved by sub services and the shape + // of the rootvalue already contains the aliased fields as responseNames return wrapSchemaWithAliasResolver(schema); } @@ -485,7 +401,7 @@ export class ApolloGateway implements GraphQLService { } this.onSchemaChangeListeners.add(callback); - if (!this.pollingTimer) this.pollServices(); + if (!this.pollingTimer) this.startPollingServices(); return () => { this.onSchemaChangeListeners.delete(callback); @@ -496,28 +412,17 @@ export class ApolloGateway implements GraphQLService { }; } - private async pollServices() { - if (this.pollingTimer) clearTimeout(this.pollingTimer); + private startPollingServices() { + if (this.pollingTimer) clearInterval(this.pollingTimer); - try { - await this.updateComposition(); - } catch (err) { - this.logger.error(err && err.message || err); - } + this.pollingTimer = setInterval(() => { + this.updateComposition(); + }, this.experimental_pollInterval || 10000); - // Sleep for the specified pollInterval before kicking off another round of polling - await new Promise(res => { - this.pollingTimer = setTimeout( - res, - this.experimental_pollInterval || 10000, - ); - // Prevent the Node.js event loop from remaining active (and preventing, - // e.g. process shutdown) by calling `unref` on the `Timeout`. For more - // information, see https://nodejs.org/api/timers.html#timers_timeout_unref. - this.pollingTimer?.unref(); - }); - - this.pollServices(); + // Prevent the Node.js event loop from remaining active (and preventing, + // e.g. process shutdown) by calling `unref` on the `Timeout`. For more + // information, see https://nodejs.org/api/timers.html#timers_timeout_unref. + this.pollingTimer.unref(); } private createAndCacheDataSource( @@ -530,28 +435,22 @@ export class ApolloGateway implements GraphQLService { ) return this.serviceMap[serviceDef.name].dataSource; - const dataSource = this.createDataSource(serviceDef); - - // Cache the created DataSource - this.serviceMap[serviceDef.name] = { url: serviceDef.url, dataSource }; - - return dataSource; - } - - private createDataSource( - serviceDef: ServiceEndpointDefinition, - ): GraphQLDataSource { if (!serviceDef.url && !isLocalConfig(this.config)) { this.logger.error( `Service definition for service ${serviceDef.name} is missing a url`, ); } - return this.config.buildService + const dataSource = this.config.buildService ? this.config.buildService(serviceDef) : new RemoteGraphQLDataSource({ url: serviceDef.url, }); + + // Cache the created DataSource + this.serviceMap[serviceDef.name] = { url: serviceDef.url, dataSource }; + + return dataSource; } protected createServices(services: ServiceEndpointDefinition[]) { @@ -563,38 +462,6 @@ export class ApolloGateway implements GraphQLService { protected async loadServiceDefinitions( config: GatewayConfig, ): ReturnType { - // This helper avoids the repetition of options in the two cases this method - // is invoked below. It is a helper, rather than an options object, since it - // depends on the presence of `this.engineConfig`, which is guarded against - // further down in this method in two separate places. - const getRemoteConfig = (engineConfig: GraphQLServiceEngineConfig) => { - return getServiceDefinitionsFromStorage({ - graphId: engineConfig.graphId, - apiKeyHash: engineConfig.apiKeyHash, - graphVariant: engineConfig.graphVariant, - federationVersion: - (config as ManagedGatewayConfig).federationVersion || 1, - fetcher: this.fetcher, - }); - }; - - if (isLocalConfig(config) || isRemoteConfig(config)) { - if (this.engineConfig && !this.warnedStates.remoteWithLocalConfig) { - // Only display this warning once per start-up. - this.warnedStates.remoteWithLocalConfig = true; - // This error helps avoid common misconfiguration. - // We don't await this because a local configuration should assume - // remote is unavailable for one reason or another. - getRemoteConfig(this.engineConfig).then(() => { - this.logger.warn( - "A local gateway service list is overriding an Apollo Graph " + - "Manager managed configuration. To use the managed " + - "configuration, do not specify a service list locally.", - ); - }).catch(() => {}); // Don't mind errors if managed config is missing. - } - } - if (isLocalConfig(config)) { return { isNewSchema: false }; } @@ -620,7 +487,13 @@ export class ApolloGateway implements GraphQLService { ); } - return getRemoteConfig(this.engineConfig); + return getServiceDefinitionsFromStorage({ + graphId: this.engineConfig.graphId, + apiKeyHash: this.engineConfig.apiKeyHash, + graphVariant: this.engineConfig.graphVariant, + federationVersion: config.federationVersion || 1, + fetcher: this.fetcher + }); } // XXX Nothing guarantees that the only errors thrown or returned in @@ -629,7 +502,7 @@ export class ApolloGateway implements GraphQLService { // are unlikely to show up as GraphQLErrors. Do we need to use // formatApolloErrors or something? public executor = async ( - requestContext: GraphQLRequestContextExecutionDidStart, + requestContext: RequestContext, ): Promise => { const { request, document, queryHash } = requestContext; const queryPlanStoreKey = queryHash + (request.operationName || ''); @@ -676,11 +549,7 @@ export class ApolloGateway implements GraphQLService { // is returning a non-native `Promise` (e.g. Bluebird, etc.). Promise.resolve( this.queryPlanStore.set(queryPlanStoreKey, queryPlan), - ).catch(err => - this.logger.warn( - 'Could not store queryPlan' + ((err && err.message) || err), - ), - ); + ).catch(err => this.logger.warn('Could not store queryPlan', err)); } } @@ -742,7 +611,7 @@ export class ApolloGateway implements GraphQLService { }; protected validateIncomingRequest( - requestContext: GraphQLRequestContextExecutionDidStart, + requestContext: RequestContext, operationContext: OperationContext, ) { // casting out of `readonly` @@ -776,7 +645,7 @@ export class ApolloGateway implements GraphQLService { public async stop() { if (this.pollingTimer) { - clearTimeout(this.pollingTimer); + clearInterval(this.pollingTimer); this.pollingTimer = undefined; } } diff --git a/packages/apollo-gateway/src/loadServicesFromRemoteEndpoint.ts b/packages/apollo-gateway/src/loadServicesFromRemoteEndpoint.ts index c4dd8b9f3c8..a11840c7911 100644 --- a/packages/apollo-gateway/src/loadServicesFromRemoteEndpoint.ts +++ b/packages/apollo-gateway/src/loadServicesFromRemoteEndpoint.ts @@ -2,7 +2,7 @@ import { GraphQLRequest } from 'apollo-server-types'; import { parse } from 'graphql'; import { Headers, HeadersInit } from 'node-fetch'; import { GraphQLDataSource } from './datasources/types'; -import { Experimental_UpdateServiceDefinitions, SERVICE_DEFINITION_QUERY } from './'; +import { Experimental_UpdateServiceDefinitions } from './'; import { ServiceDefinition } from '@apollo/federation'; export async function getServiceDefinitionsFromRemoteEndpoint({ @@ -26,51 +26,57 @@ export async function getServiceDefinitionsFromRemoteEndpoint({ let isNewSchema = false; // for each service, fetch its introspection schema - const promiseOfServiceList = serviceList.map(({ name, url, dataSource }) => { - if (!url) { - throw new Error( - `Tried to load schema for '${name}' but no 'url' was specified.`); - } + 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`); + } - const request: GraphQLRequest = { - query: SERVICE_DEFINITION_QUERY, - http: { - url, - method: 'POST', - headers: new Headers(headers), - }, - }; + const request: GraphQLRequest = { + query: 'query GetServiceDefinition { _service { sdl } }', + http: { + url, + method: 'POST', + headers: new Headers(headers), + }, + }; - return dataSource - .process({ request, context: {} }) - .then(({ data, errors }): ServiceDefinition => { - if (data && !errors) { - const typeDefs = data._service.sdl as string; - const previousDefinition = serviceSdlCache.get(name); - // this lets us know if any downstream service has changed - // and we need to recalculate the schema - if (previousDefinition !== typeDefs) { - isNewSchema = true; + return dataSource + .process({ request, context: {} }) + .then(({ data, errors }) => { + if (data && !errors) { + const typeDefs = data._service.sdl as string; + const previousDefinition = serviceSdlCache.get(name); + // this lets us know if any downstream service has changed + // and we need to recalculate the schema + if (previousDefinition !== typeDefs) { + isNewSchema = true; + } + serviceSdlCache.set(name, typeDefs); + return { + name, + url, + typeDefs: parse(typeDefs), + }; } - serviceSdlCache.set(name, typeDefs); - return { - name, - url, - typeDefs: parse(typeDefs), - }; - } - throw new Error(errors?.map(e => e.message).join("\n")); - }) - .catch(err => { - const errorMessage = - `Couldn't load service definitions for "${name}" at ${url}` + - (err && err.message ? ": " + err.message || err : ""); + // XXX handle local errors better for local development + if (errors) { + errors.forEach(console.error); + } - throw new Error(errorMessage); - }); - }); + return false; + }) + .catch(error => { + console.warn( + `Encountered error when loading ${name} at ${url}: ${error.message}`, + ); + return false; + }); + }), + ).then(serviceDefinitions => + serviceDefinitions.filter(Boolean), + )) as ServiceDefinition[]; - const serviceDefinitions = await Promise.all(promiseOfServiceList); return { serviceDefinitions, isNewSchema } } diff --git a/packages/apollo-gateway/src/loadServicesFromStorage.ts b/packages/apollo-gateway/src/loadServicesFromStorage.ts index e1577a7e39a..89d8f5bf260 100644 --- a/packages/apollo-gateway/src/loadServicesFromStorage.ts +++ b/packages/apollo-gateway/src/loadServicesFromStorage.ts @@ -50,57 +50,6 @@ 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, @@ -117,9 +66,9 @@ export async function getServiceDefinitionsFromStorage({ // fetch the storage secret const storageSecretUrl = getStorageSecretUrl(graphId, apiKeyHash); - // The storage secret is a JSON string (e.g. `"secret"`). - const secret: string = - await fetchApolloGcs(fetcher, storageSecretUrl).then(res => res.json()); + const secret: string = await fetcher(storageSecretUrl).then(response => + response.json(), + ); if (!graphVariant) { graphVariant = 'current'; @@ -127,19 +76,17 @@ export async function getServiceDefinitionsFromStorage({ const baseUrl = `${urlPartialSchemaBase}/${secret}/${graphVariant}/v${federationVersion}`; - const compositionConfigResponse = - await fetchApolloGcs(fetcher, `${baseUrl}/composition-config-link`); + const response = await fetcher(`${baseUrl}/composition-config-link`); - if (compositionConfigResponse.status === 304) { + if (response.status === 304) { return { isNewSchema: false }; } - const linkFileResult: LinkFileResult = await compositionConfigResponse.json(); + const linkFileResult: LinkFileResult = await response.json(); - const compositionMetadata: CompositionMetadata = await fetchApolloGcs( - fetcher, + const compositionMetadata: CompositionMetadata = await fetcher( `${urlPartialSchemaBase}/${linkFileResult.configPath}`, - ).then(res => res.json()); + ).then(response => response.json()); // It's important to maintain the original order here const serviceDefinitions = await Promise.all( diff --git a/packages/apollo-server-azure-functions/package.json b/packages/apollo-server-azure-functions/package.json index 1f2b267c1e1..eb569b7eed8 100644 --- a/packages/apollo-server-azure-functions/package.json +++ b/packages/apollo-server-azure-functions/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-azure-functions", - "version": "2.12.0", + "version": "2.11.0", "description": "Production-ready Node.js GraphQL server for Azure Functions", "keywords": [ "GraphQL", @@ -36,6 +36,6 @@ "apollo-server-integration-testsuite": "file:../apollo-server-integration-testsuite" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-server-cache-redis/package.json b/packages/apollo-server-cache-redis/package.json index 65cee46fe15..0243ecc77b5 100644 --- a/packages/apollo-server-cache-redis/package.json +++ b/packages/apollo-server-cache-redis/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-cache-redis", - "version": "1.1.6", + "version": "1.1.5", "author": "opensource@apollographql.com", "license": "MIT", "repository": { diff --git a/packages/apollo-server-cache-redis/src/RedisCache.ts b/packages/apollo-server-cache-redis/src/RedisCache.ts index 22fe9a1977f..b45998dea98 100644 --- a/packages/apollo-server-cache-redis/src/RedisCache.ts +++ b/packages/apollo-server-cache-redis/src/RedisCache.ts @@ -11,13 +11,13 @@ export class RedisCache implements TestableKeyValueCache { ttl: 300, }; - private loader: DataLoader; + private loader: DataLoader; constructor(options?: RedisOptions) { const client = new Redis(options); this.client = client; - this.loader = new DataLoader(keys => client.mget(...keys), { + this.loader = new DataLoader(keys => this.client.mget(keys), { cache: false, }); } diff --git a/packages/apollo-server-cache-redis/src/RedisClusterCache.ts b/packages/apollo-server-cache-redis/src/RedisClusterCache.ts index 401799a9d52..79bd73c83f0 100644 --- a/packages/apollo-server-cache-redis/src/RedisClusterCache.ts +++ b/packages/apollo-server-cache-redis/src/RedisClusterCache.ts @@ -12,14 +12,14 @@ export class RedisClusterCache implements KeyValueCache { ttl: 300, }; - private loader: DataLoader; + private loader: DataLoader; constructor(nodes: ClusterNode[], options?: ClusterOptions) { - const client = this.client = new Redis.Cluster(nodes, options); + this.client = new Redis.Cluster(nodes, options); this.loader = new DataLoader( (keys = []) => - Promise.all(keys.map(key => client.get(key).catch(() => null))), + Promise.all(keys.map(key => this.client.get(key).catch(() => null))), { cache: false }, ); } diff --git a/packages/apollo-server-cloud-functions/package.json b/packages/apollo-server-cloud-functions/package.json index f04f58019d1..e07a8d0c305 100644 --- a/packages/apollo-server-cloud-functions/package.json +++ b/packages/apollo-server-cloud-functions/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-cloud-functions", - "version": "2.12.0", + "version": "2.11.0", "description": "Production-ready Node.js GraphQL server for Google Cloud Functions", "keywords": [ "GraphQL", @@ -35,6 +35,6 @@ "apollo-server-integration-testsuite": "file:../apollo-server-integration-testsuite" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-server-cloudflare/package.json b/packages/apollo-server-cloudflare/package.json index c7d6d965ccf..835cb36ad94 100644 --- a/packages/apollo-server-cloudflare/package.json +++ b/packages/apollo-server-cloudflare/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-cloudflare", - "version": "2.12.0", + "version": "2.11.0", "description": "Production-ready Node.js GraphQL server for Cloudflare workers", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -26,6 +26,6 @@ "apollo-server-types": "file:../apollo-server-types" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-server-core/package.json b/packages/apollo-server-core/package.json index 3615c6e02a1..1f666b46952 100644 --- a/packages/apollo-server-core/package.json +++ b/packages/apollo-server-core/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-core", - "version": "2.12.0", + "version": "2.11.0", "description": "Core engine for Apollo GraphQL server", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -42,12 +42,11 @@ "graphql-tag": "^2.9.2", "graphql-tools": "^4.0.0", "graphql-upload": "^8.0.2", - "loglevel": "^1.6.7", "sha.js": "^2.4.11", "subscriptions-transport-ws": "^0.9.11", "ws": "^6.0.0" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-server-core/src/ApolloServer.ts b/packages/apollo-server-core/src/ApolloServer.ts index 5b94b5ea022..01755da1004 100644 --- a/packages/apollo-server-core/src/ApolloServer.ts +++ b/packages/apollo-server-core/src/ApolloServer.ts @@ -4,7 +4,6 @@ import { GraphQLParseOptions, } from 'graphql-tools'; import { Server as HttpServer } from 'http'; -import loglevel from 'loglevel'; import { execute, GraphQLSchema, @@ -69,7 +68,6 @@ import { import { Headers } from 'apollo-server-env'; import { buildServiceDefinition } from '@apollographql/apollo-tools'; -import { Logger } from "apollo-server-types"; const NoIntrospection = (context: ValidationContext) => ({ Field(node: FieldDefinitionNode) { @@ -139,7 +137,6 @@ type SchemaDerivedData = { }; export class ApolloServerBase { - private logger: Logger; public subscriptionsPath?: string; public graphqlPath: string = '/graphql'; public requestOptions: Partial> = Object.create(null); @@ -194,29 +191,8 @@ export class ApolloServerBase { ...requestOptions } = config; - // Setup logging facilities - if (config.logger) { - this.logger = config.logger; - } else { - // If the user didn't provide their own logger, we'll initialize one. - const loglevelLogger = loglevel.getLogger("apollo-server"); - - // We don't do much logging in Apollo Server right now. There's a notion - // of a `debug` flag, but it doesn't do much besides change stack traces - // in some error messages, but it would be odd for it to not introduce - // debug or higher level errors (which includes `info`, if we happen to - // start introducing those. We'll default to `warn` as a sensible default - // of things you'd probably want to be alerted to. - if (this.config.debug === true) { - loglevelLogger.setLevel(loglevel.levels.DEBUG); - } else { - loglevelLogger.setLevel(loglevel.levels.WARN); - } - - this.logger = loglevelLogger; - } - 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`', ); @@ -233,7 +209,7 @@ export class ApolloServerBase { // once per run, so we place the env check inside the constructor. If env // should be used outside of the constructor context, place it as a private // or protected field of the class instead of a global. Keeping the read in - // the constructor enables testing of different environments + // the contructor enables testing of different environments const isDev = process.env.NODE_ENV !== 'production'; // if this is local dev, introspection should turned on @@ -298,7 +274,7 @@ export class ApolloServerBase { if (uploads !== false && !forbidUploadsForTesting) { if (this.supportsUploads()) { if (!runtimeSupportsUploads) { - printNodeFileUploadsMessage(this.logger); + printNodeFileUploadsMessage(); throw new Error( '`graphql-upload` is no longer supported on Node.js < v8.5.0. ' + 'See https://bit.ly/gql-upload-node-6.', @@ -319,13 +295,8 @@ export class ApolloServerBase { } } + // Normalize the legacy option maskErrorDetails. if (engine && typeof engine === 'object') { - // Use the `ApolloServer` logger unless a more granular logger is set. - if (!engine.logger) { - engine.logger = this.logger; - } - - // Normalize the legacy option maskErrorDetails. if (engine.maskErrorDetails && engine.rewriteError) { throw new Error("Can't set both maskErrorDetails and rewriteError!"); } else if ( @@ -341,7 +312,7 @@ export class ApolloServerBase { // In an effort to avoid over-exposing the API key itself, extract the // service ID from the API key for plugins which only needs service ID. - // The truthiness of this value can also be used in other forks of logic + // The truthyness of this value can also be used in other forks of logic // related to Engine, as is the case with EngineReportingAgent just below. this.engineServiceId = getEngineServiceId(engine); const apiKey = getEngineApiKey(engine); @@ -354,15 +325,13 @@ export class ApolloServerBase { if (this.engineServiceId) { const { EngineReportingAgent } = require('apollo-engine-reporting'); this.engineReportingAgent = new EngineReportingAgent( - typeof engine === 'object' ? engine : Object.create({ - logger: this.logger, - }), + typeof engine === 'object' ? engine : Object.create(null), ); // Don't add the extension here (we want to add it later in generateSchemaDerivedData). } if (gateway && subscriptions !== false) { - // TODO: this could be handled by adjusting the typings to keep gateway configs and non-gateway configs separate. + // TODO: this could be handled by adjusting the typings to keep gateway configs and non-gateway configs seprate. throw new Error( [ 'Subscriptions are not yet compatible with the gateway.', @@ -454,27 +423,13 @@ export class ApolloServerBase { } : undefined; - // 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 propagate to the client. We will, however, log the error - // for observation in the logs. - const message = "This data graph is missing a valid configuration."; - this.logger.error(message + " " + (err && err.message || err)); - throw new Error( - message + " More details may be available in the server logs."); - }); + return gateway.load({ engine: engineConfig }).then(config => { + this.requestOptions.executor = config.executor; + return config.schema; + }); } - let constructedSchema: GraphQLSchema; + let constructedSchema; if (schema) { constructedSchema = schema; } else if (modules) { @@ -572,7 +527,7 @@ export class ApolloServerBase { // their own gateway or running a federated service on its own. Nonetheless, in // the likely case it was accidental, we warn users that they should only report // metrics from the Gateway. - this.logger.warn( + console.warn( "It looks like you're running a federated schema and you've configured your service " + 'to report metrics to Apollo Graph Manager. You should only configure your Apollo gateway ' + 'to report metrics to Apollo Graph Manager.', @@ -611,22 +566,8 @@ export class ApolloServerBase { } protected async willStart() { - 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 { schema, schemaHash } = await this.schemaDerivedData; const service: GraphQLServiceContext = { - logger: this.logger, schema: schema, schemaHash: schemaHash, engine: { @@ -833,7 +774,6 @@ export class ApolloServerBase { return { schema, - logger: this.logger, plugins: this.plugins, documentStore, extensions, @@ -854,14 +794,20 @@ export class ApolloServerBase { } public async executeOperation(request: GraphQLRequest) { - const options = await this.graphQLServerOptions(); + let options; + + try { + options = await this.graphQLServerOptions(); + } catch (e) { + e.message = `Invalid options provided to ApolloServer: ${e.message}`; + throw new Error(e); + } if (typeof options.context === 'function') { options.context = (options.context as () => never)(); } const requestCtx: GraphQLRequestContext = { - logger: this.logger, request, context: options.context || Object.create(null), cache: options.cache!, @@ -876,8 +822,8 @@ export class ApolloServerBase { } } -function printNodeFileUploadsMessage(logger: Logger) { - logger.error( +function printNodeFileUploadsMessage() { + console.error( [ '*****************************************************************', '* *', diff --git a/packages/apollo-server-core/src/__tests__/logger.test.ts b/packages/apollo-server-core/src/__tests__/logger.test.ts deleted file mode 100644 index a4a34b45b30..00000000000 --- a/packages/apollo-server-core/src/__tests__/logger.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { ApolloServerBase } from '../..'; -import { Logger } from "apollo-server-types"; -import { PassThrough } from "stream"; -import gql from "graphql-tag"; - -import * as winston from "winston"; -import WinstonTransport from 'winston-transport'; -import * as bunyan from "bunyan"; -import * as loglevel from "loglevel"; -// We are testing an older version of `log4js` which uses older ECMAScript -// in order to still support testing on Node.js 6. -// This should be updated when bump the semver major for AS3. -import * as log4js from "log4js"; - -const LOWEST_LOG_LEVEL = "debug"; - -const KNOWN_DEBUG_MESSAGE = "The request has started."; - -async function triggerLogMessage(loggerToUse: Logger) { - await (new ApolloServerBase({ - typeDefs: gql` - type Query { - field: String! - } - `, - logger: loggerToUse, - plugins: [ - { - requestDidStart({ logger }) { - logger.debug(KNOWN_DEBUG_MESSAGE); - } - } - ] - })).executeOperation({ - query: '{ field }' - }); -} - -describe("logger", () => { - it("works with 'winston'", async () => { - const sink = jest.fn(); - const transport = new class extends WinstonTransport { - constructor() { - super({ - format: winston.format.json(), - }); - } - - log(info: any) { - sink(info); - } - }; - - const logger = winston.createLogger({ level: 'debug' }).add(transport); - - await triggerLogMessage(logger); - - expect(sink).toHaveBeenCalledWith(expect.objectContaining({ - level: LOWEST_LOG_LEVEL, - message: KNOWN_DEBUG_MESSAGE, - })); - }); - - it("works with 'bunyan'", async () => { - const sink = jest.fn(); - - // Bunyan uses streams for its logging implementations. - const writable = new PassThrough(); - writable.on("data", data => sink(JSON.parse(data.toString()))); - - const logger = bunyan.createLogger({ - name: "test-logger-bunyan", - streams: [{ - level: LOWEST_LOG_LEVEL, - stream: writable, - }] - }); - - await triggerLogMessage(logger); - - expect(sink).toHaveBeenCalledWith(expect.objectContaining({ - level: bunyan.DEBUG, - msg: KNOWN_DEBUG_MESSAGE, - })); - }); - - it("works with 'loglevel'", async () => { - const sink = jest.fn(); - - const logger = loglevel.getLogger("test-logger-loglevel") - logger.methodFactory = (_methodName, level): loglevel.LoggingMethod => - (message) => sink({ level, message }); - - // The `setLevel` method must be called after overwriting `methodFactory`. - // This is an intentional API design pattern of the loglevel package: - // https://www.npmjs.com/package/loglevel#writing-plugins - logger.setLevel(loglevel.levels.DEBUG); - - await triggerLogMessage(logger); - - expect(sink).toHaveBeenCalledWith({ - level: loglevel.levels.DEBUG, - message: KNOWN_DEBUG_MESSAGE, - }); - }); - - it("works with 'log4js'", async () => { - const sink = jest.fn(); - - log4js.configure({ - appenders: { - custom: { - type: { - configure: () => - (loggingEvent: log4js.LoggingEvent) => sink(loggingEvent) - } - } - }, - categories: { - default: { - appenders: ['custom'], - level: LOWEST_LOG_LEVEL, - } - } - }); - - const logger = log4js.getLogger(); - logger.level = LOWEST_LOG_LEVEL; - - await triggerLogMessage(logger); - - expect(sink).toHaveBeenCalledWith(expect.objectContaining({ - level: log4js.levels.DEBUG, - data: [KNOWN_DEBUG_MESSAGE], - })); - }); -}); diff --git a/packages/apollo-server-core/src/graphqlOptions.ts b/packages/apollo-server-core/src/graphqlOptions.ts index e6da70d37b0..b9abdd0f949 100644 --- a/packages/apollo-server-core/src/graphqlOptions.ts +++ b/packages/apollo-server-core/src/graphqlOptions.ts @@ -17,14 +17,12 @@ import { ValueOrPromise, GraphQLResponse, GraphQLRequestContext, - Logger, } from 'apollo-server-types'; /* * GraphQLServerOptions * * - schema: an executable GraphQL schema used to fulfill requests. - * - (optional) logger: a `Logger`-compatible implementation to be used for server-level messages. * - (optional) formatError: Formatting function applied to all errors before response is sent * - (optional) rootValue: rootValue passed to GraphQL execution, or a function to resolving the rootValue from the DocumentNode * - (optional) context: the context passed to GraphQL execution @@ -42,7 +40,6 @@ export interface GraphQLServerOptions< TRootValue = any > { schema: GraphQLSchema; - logger?: Logger; formatError?: (error: GraphQLError) => GraphQLFormattedError; rootValue?: ((parsedQuery: DocumentNode) => TRootValue) | TRootValue; context?: TContext | (() => never); diff --git a/packages/apollo-server-core/src/requestPipeline.ts b/packages/apollo-server-core/src/requestPipeline.ts index 07a640c1924..786ade95b37 100644 --- a/packages/apollo-server-core/src/requestPipeline.ts +++ b/packages/apollo-server-core/src/requestPipeline.ts @@ -7,10 +7,8 @@ import { ExecutionArgs, GraphQLError, GraphQLFormattedError, - validate as graphqlValidate, - parse as graphqlParse, - execute as graphqlExecute, } from 'graphql'; +import * as graphql from 'graphql'; import { GraphQLExtension, GraphQLExtensionStack, @@ -45,13 +43,6 @@ import { import { ApolloServerPlugin, GraphQLRequestListener, - GraphQLRequestContextExecutionDidStart, - GraphQLRequestContextResponseForOperation, - GraphQLRequestContextDidResolveOperation, - GraphQLRequestContextParsingDidStart, - GraphQLRequestContextValidationDidStart, - GraphQLRequestContextWillSendResponse, - GraphQLRequestContextDidEncounterErrors, } from 'apollo-server-plugin-base'; import { Dispatcher } from './utils/dispatcher'; @@ -117,11 +108,6 @@ export async function processGraphQLRequest( config: GraphQLRequestPipelineConfig, requestContext: Mutable>, ): Promise { - // For legacy reasons, this exported method may exist without a `logger` on - // the context. We'll need to make sure we account for that, even though - // all of our own machinery will certainly set it now. - const logger = requestContext.logger || console; - let cacheControlExtension: CacheControlExtension | undefined; const extensionStack = initializeExtensionStack(); (requestContext.context as any)._extensionStack = extensionStack; @@ -188,7 +174,7 @@ export async function processGraphQLRequest( } // We won't write to the persisted query cache until later. - // Deferring the writing gives plugins the ability to "win" from use of + // Defering the writing gives plugins the ability to "win" from use of // the cache, but also have their say in whether or not the cache is // written to (by interrupting the request with an error). metrics.persistedQueryRegister = true; @@ -229,9 +215,9 @@ export async function processGraphQLRequest( try { requestContext.document = await config.documentStore.get(queryHash); } catch (err) { - logger.warn( - 'An error occurred while attempting to read from the documentStore. ' - + (err && err.message) || err, + console.warn( + 'An error occurred while attempting to read from the documentStore.', + err, ); } } @@ -241,7 +227,10 @@ export async function processGraphQLRequest( if (!requestContext.document) { const parsingDidEnd = await dispatcher.invokeDidStartHook( 'parsingDidStart', - requestContext as GraphQLRequestContextParsingDidStart, + requestContext as WithRequired< + typeof requestContext, + 'metrics' | 'source' + >, ); try { @@ -254,7 +243,10 @@ export async function processGraphQLRequest( const validationDidEnd = await dispatcher.invokeDidStartHook( 'validationDidStart', - requestContext as GraphQLRequestContextValidationDidStart, + requestContext as WithRequired< + typeof requestContext, + 'document' | 'source' | 'metrics' + >, ); const validationErrors = validate(requestContext.document); @@ -282,10 +274,7 @@ export async function processGraphQLRequest( Promise.resolve( config.documentStore.set(queryHash, requestContext.document), ).catch(err => - logger.warn( - 'Could not store validated document. ' + - (err && err.message) || err - ) + console.warn('Could not store validated document.', err), ); } } @@ -310,7 +299,10 @@ export async function processGraphQLRequest( try { await dispatcher.invokeHookAsync( 'didResolveOperation', - requestContext as GraphQLRequestContextDidResolveOperation, + requestContext as WithRequired< + typeof requestContext, + 'document' | 'source' | 'operation' | 'operationName' | 'metrics' + >, ); } catch (err) { // XXX: The HttpQueryError is special-cased here because we currently @@ -349,23 +341,30 @@ export async function processGraphQLRequest( } : Object.create(null), ), - ).catch(logger.warn); + ).catch(console.warn); } let response: GraphQLResponse | null = await dispatcher.invokeHooksUntilNonNull( 'responseForOperation', - requestContext as GraphQLRequestContextResponseForOperation, + requestContext as WithRequired< + typeof requestContext, + 'document' | 'source' | 'operation' | 'operationName' | 'metrics' + >, ); if (response == null) { const executionDidEnd = await dispatcher.invokeDidStartHook( 'executionDidStart', - requestContext as GraphQLRequestContextExecutionDidStart, + requestContext as WithRequired< + typeof requestContext, + 'document' | 'source' | 'operation' | 'operationName' | 'metrics' + >, ); try { - const result = await execute( - requestContext as GraphQLRequestContextExecutionDidStart, - ); + const result = await execute(requestContext as WithRequired< + typeof requestContext, + 'document' | 'operation' | 'operationName' | 'queryHash' + >); if (result.errors) { await didEncounterErrors(result.errors); @@ -426,7 +425,7 @@ export async function processGraphQLRequest( }); try { - return graphqlParse(query, parseOptions); + return graphql.parse(query, parseOptions); } finally { parsingDidEnd(); } @@ -441,14 +440,17 @@ export async function processGraphQLRequest( const validationDidEnd = extensionStack.validationDidStart(); try { - return graphqlValidate(config.schema, document, rules); + return graphql.validate(config.schema, document, rules); } finally { validationDidEnd(); } } async function execute( - requestContext: GraphQLRequestContextExecutionDidStart, + requestContext: WithRequired< + GraphQLRequestContext, + 'document' | 'operationName' | 'operation' | 'queryHash' + >, ): Promise { const { request, document } = requestContext; @@ -476,7 +478,7 @@ export async function processGraphQLRequest( // (eg apollo-engine-reporting) assumes that. return await config.executor(requestContext); } else { - return await graphqlExecute(executionArgs); + return await graphql.execute(executionArgs); } } finally { executionDidEnd(); @@ -499,7 +501,10 @@ export async function processGraphQLRequest( }).graphqlResponse; await dispatcher.invokeHookAsync( 'willSendResponse', - requestContext as GraphQLRequestContextWillSendResponse, + requestContext as WithRequired< + typeof requestContext, + 'metrics' | 'response' + >, ); return requestContext.response!; } @@ -530,7 +535,10 @@ export async function processGraphQLRequest( return await dispatcher.invokeHookAsync( 'didEncounterErrors', - requestContext as GraphQLRequestContextDidEncounterErrors, + requestContext as WithRequired< + typeof requestContext, + 'metrics' | 'source' | 'errors' + >, ); } diff --git a/packages/apollo-server-core/src/runHttpQuery.ts b/packages/apollo-server-core/src/runHttpQuery.ts index c229fe87cf7..633c50daacd 100644 --- a/packages/apollo-server-core/src/runHttpQuery.ts +++ b/packages/apollo-server-core/src/runHttpQuery.ts @@ -127,6 +127,10 @@ 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) { @@ -161,7 +165,6 @@ export async function runHttpQuery( const config = { schema: options.schema, - logger: options.logger, rootValue: options.rootValue, context: options.context || {}, validationRules: options.validationRules, @@ -251,11 +254,6 @@ export async function processHTTPRequest( // in ApolloServer#graphQLServerOptions, before runHttpQuery is invoked). const context = cloneObject(options.context); return { - // While `logger` is guaranteed by internal Apollo Server usage of - // this `processHTTPRequest` method, this method has been publicly - // exported since perhaps as far back as Apollo Server 1.x. Therefore, - // for compatibility reasons, we'll default to `console`. - logger: options.logger || console, request, response: { http: { diff --git a/packages/apollo-server-core/src/types.ts b/packages/apollo-server-core/src/types.ts index 86f24ba434f..6eeb844a07f 100644 --- a/packages/apollo-server-core/src/types.ts +++ b/packages/apollo-server-core/src/types.ts @@ -5,12 +5,7 @@ import { IMocks, GraphQLParseOptions, } from 'graphql-tools'; -import { - ValueOrPromise, - GraphQLExecutor, - GraphQLExecutionResult, - GraphQLRequestContextExecutionDidStart, -} from 'apollo-server-types'; +import { ValueOrPromise, GraphQLExecutor } 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. @@ -67,7 +62,6 @@ type BaseConfig = Pick< | 'tracing' | 'dataSources' | 'cache' - | 'logger' >; export type Unsubscriber = () => void; @@ -93,11 +87,6 @@ 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: GraphQLRequestContextExecutionDidStart, - ): ValueOrPromise; } // This configuration is shared between all integrations and should include diff --git a/packages/apollo-server-errors/package.json b/packages/apollo-server-errors/package.json index 92fb6ed0183..1d81652e7f7 100644 --- a/packages/apollo-server-errors/package.json +++ b/packages/apollo-server-errors/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-errors", - "version": "2.4.1", + "version": "2.4.0", "author": "opensource@apollographql.com", "license": "MIT", "repository": { @@ -17,6 +17,6 @@ "node": ">=6" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-server-express/package.json b/packages/apollo-server-express/package.json index a123e0597c0..2309e56bcce 100644 --- a/packages/apollo-server-express/package.json +++ b/packages/apollo-server-express/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-express", - "version": "2.12.0", + "version": "2.11.0", "description": "Production-ready Node.js GraphQL server for Express and Connect", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -47,6 +47,6 @@ "apollo-server-integration-testsuite": "file:../apollo-server-integration-testsuite" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-server-fastify/package.json b/packages/apollo-server-fastify/package.json index 7c20f460d5a..d55f19ddbd1 100644 --- a/packages/apollo-server-fastify/package.json +++ b/packages/apollo-server-fastify/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-fastify", - "version": "2.12.0", + "version": "2.11.0", "description": "Production-ready Node.js GraphQL server for Fastify", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -37,6 +37,6 @@ "apollo-server-integration-testsuite": "file:../apollo-server-integration-testsuite" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-server-hapi/package.json b/packages/apollo-server-hapi/package.json index 7d2fbed991d..aa4d6dad16a 100644 --- a/packages/apollo-server-hapi/package.json +++ b/packages/apollo-server-hapi/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-hapi", - "version": "2.12.0", + "version": "2.11.0", "description": "Production-ready Node.js GraphQL server for Hapi", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -37,6 +37,6 @@ "apollo-server-integration-testsuite": "file:../apollo-server-integration-testsuite" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-server-integration-testsuite/package.json b/packages/apollo-server-integration-testsuite/package.json index 954fc609822..d37ad357d0d 100644 --- a/packages/apollo-server-integration-testsuite/package.json +++ b/packages/apollo-server-integration-testsuite/package.json @@ -1,7 +1,7 @@ { "name": "apollo-server-integration-testsuite", "private": true, - "version": "2.12.0", + "version": "2.11.0", "description": "Apollo Server Integrations testsuite", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-integration-testsuite/src/ApolloServer.ts b/packages/apollo-server-integration-testsuite/src/ApolloServer.ts index 176033dd152..05219c78d8b 100644 --- a/packages/apollo-server-integration-testsuite/src/ApolloServer.ts +++ b/packages/apollo-server-integration-testsuite/src/ApolloServer.ts @@ -116,31 +116,24 @@ 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, reject) => { + }>(resolve => { eventuallyAssigned.resolveLoad = ({ schema, executor }) => { resolve({ schema, executor }); }; - eventuallyAssigned.rejectLoad = (err: Error) => { - reject(err); - }; }); const mockedGateway: GraphQLService = { - executor, load: options => { optionsSpy(options); return mockedLoadResults; @@ -361,13 +354,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({ @@ -383,47 +376,6 @@ 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 { @@ -2881,13 +2833,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, @@ -2943,6 +2895,7 @@ 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 = () => @@ -2952,8 +2905,6 @@ export function testApolloServer( }; }); - const { gateway, triggers } = makeGatewayMock({ executor }); - triggers.resolveLoad({ schema, executor }); const { url: uri } = await createApolloServer({ gateway, @@ -2990,6 +2941,8 @@ export function testApolloServer( }), }); + const { gateway, triggers } = makeGatewayMock(); + const makeEventuallyResolvingPromise = val => { let resolver; const promise = new Promise( @@ -3015,8 +2968,6 @@ export function testApolloServer( ? p2 : p3; - const { gateway, triggers } = makeGatewayMock({ executor }); - triggers.resolveLoad({ schema: makeQueryTypeWithField('testString1'), executor, diff --git a/packages/apollo-server-koa/package.json b/packages/apollo-server-koa/package.json index 674891a0ba0..fcb19526726 100644 --- a/packages/apollo-server-koa/package.json +++ b/packages/apollo-server-koa/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-koa", - "version": "2.12.0", + "version": "2.11.0", "description": "Production-ready Node.js GraphQL server for Koa", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -48,6 +48,6 @@ "apollo-server-integration-testsuite": "file:../apollo-server-integration-testsuite" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-server-lambda/package.json b/packages/apollo-server-lambda/package.json index a33da3c6388..1d9ac1319ab 100644 --- a/packages/apollo-server-lambda/package.json +++ b/packages/apollo-server-lambda/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-lambda", - "version": "2.12.0", + "version": "2.11.0", "description": "Production-ready Node.js GraphQL server for AWS Lambda", "keywords": [ "GraphQL", @@ -36,6 +36,6 @@ "apollo-server-integration-testsuite": "file:../apollo-server-integration-testsuite" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-server-micro/package.json b/packages/apollo-server-micro/package.json index 73804efb918..e5b2ea588ad 100644 --- a/packages/apollo-server-micro/package.json +++ b/packages/apollo-server-micro/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-micro", - "version": "2.12.0", + "version": "2.11.0", "description": "Production-ready Node.js GraphQL server for Micro", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-plugin-base/package.json b/packages/apollo-server-plugin-base/package.json index db9b3b366ea..e0e19583523 100644 --- a/packages/apollo-server-plugin-base/package.json +++ b/packages/apollo-server-plugin-base/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-plugin-base", - "version": "0.7.1", + "version": "0.7.0", "description": "Apollo Server plugin base classes", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -14,6 +14,6 @@ "apollo-server-types": "file:../apollo-server-types" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-server-plugin-base/src/index.ts b/packages/apollo-server-plugin-base/src/index.ts index 5ca040fd617..bbef6d15d83 100644 --- a/packages/apollo-server-plugin-base/src/index.ts +++ b/packages/apollo-server-plugin-base/src/index.ts @@ -5,23 +5,7 @@ import { GraphQLResponse, ValueOrPromise, WithRequired, - GraphQLRequestContextParsingDidStart, - GraphQLRequestContextValidationDidStart, - GraphQLRequestContextDidResolveOperation, - GraphQLRequestContextDidEncounterErrors, - GraphQLRequestContextResponseForOperation, - GraphQLRequestContextExecutionDidStart, - GraphQLRequestContextWillSendResponse, } from 'apollo-server-types'; - -// We re-export all of these so plugin authors only need to depend on a single -// package. The overall concept of `apollo-server-types` and this package -// is that they not depend directly on "core", in order to avoid close -// coupling of plugin support with server versions. They are duplicated -// concepts right now where one package is intended to be for public plugin -// exposure, while the other (`-types`) is meant to be used internally. -// In the future, `apollo-server-types` and `apollo-server-plugin-base` will -// probably roll into the same "types" package, but that is not today! export { GraphQLServiceContext, GraphQLRequestContext, @@ -29,13 +13,6 @@ export { GraphQLResponse, ValueOrPromise, WithRequired, - GraphQLRequestContextParsingDidStart, - GraphQLRequestContextValidationDidStart, - GraphQLRequestContextDidResolveOperation, - GraphQLRequestContextDidEncounterErrors, - GraphQLRequestContextResponseForOperation, - GraphQLRequestContextExecutionDidStart, - GraphQLRequestContextWillSendResponse, }; export interface ApolloServerPlugin = Record> { @@ -47,16 +24,28 @@ export interface ApolloServerPlugin = Recor export interface GraphQLRequestListener> { parsingDidStart?( - requestContext: GraphQLRequestContextParsingDidStart, + requestContext: WithRequired< + GraphQLRequestContext, + 'metrics' | 'source' + >, ): ((err?: Error) => void) | void; validationDidStart?( - requestContext: GraphQLRequestContextValidationDidStart, + requestContext: WithRequired< + GraphQLRequestContext, + 'metrics' | 'source' | 'document' + >, ): ((err?: ReadonlyArray) => void) | void; didResolveOperation?( - requestContext: GraphQLRequestContextDidResolveOperation, + requestContext: WithRequired< + GraphQLRequestContext, + 'metrics' | 'source' | 'document' | 'operationName' | 'operation' + >, ): ValueOrPromise; didEncounterErrors?( - requestContext: GraphQLRequestContextDidEncounterErrors, + requestContext: WithRequired< + GraphQLRequestContext, + 'metrics' | 'source' | 'errors' + >, ): ValueOrPromise; // If this hook is defined, it is invoked immediately before GraphQL execution // would take place. If its return value resolves to a non-null @@ -64,12 +53,21 @@ export interface GraphQLRequestListener> { // Hooks from different plugins are invoked in series and the first non-null // response is used. responseForOperation?( - requestContext: GraphQLRequestContextResponseForOperation, + requestContext: WithRequired< + GraphQLRequestContext, + 'metrics' | 'source' | 'document' | 'operationName' | 'operation' + >, ): ValueOrPromise; executionDidStart?( - requestContext: GraphQLRequestContextExecutionDidStart, + requestContext: WithRequired< + GraphQLRequestContext, + 'metrics' | 'source' | 'document' | 'operationName' | 'operation' + >, ): ((err?: Error) => void) | void; willSendResponse?( - requestContext: GraphQLRequestContextWillSendResponse, + requestContext: WithRequired< + GraphQLRequestContext, + 'metrics' | 'response' + >, ): ValueOrPromise; } diff --git a/packages/apollo-server-plugin-response-cache/package.json b/packages/apollo-server-plugin-response-cache/package.json index f7f5a423846..5d0836d2ae7 100644 --- a/packages/apollo-server-plugin-response-cache/package.json +++ b/packages/apollo-server-plugin-response-cache/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-plugin-response-cache", - "version": "0.4.1", + "version": "0.4.0", "description": "Apollo Server full query response cache", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -27,6 +27,6 @@ "apollo-server-types": "file:../apollo-server-types" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts b/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts index bd78388e3ed..f942d49ca6b 100644 --- a/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts +++ b/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts @@ -222,8 +222,6 @@ export default function plugin( }, async willSendResponse(requestContext) { - const logger = requestContext.logger || console; - if (!isGraphQLQuery(requestContext)) { return; } @@ -297,13 +295,13 @@ export default function plugin( // InMemoryLRUCache synchronously). cache .set(key, serializedValue, { ttl: overallCachePolicy!.maxAge }) - .catch(logger.warn); + .catch(console.warn); } const isPrivate = overallCachePolicy.scope === CacheScope.Private; if (isPrivate) { if (!options.sessionId) { - logger.warn( + console.warn( 'A GraphQL response used @cacheControl or setCacheHint to set cache hints with scope ' + "Private, but you didn't define the sessionId hook for " + 'apollo-server-plugin-response-cache. Not caching.', diff --git a/packages/apollo-server-testing/package.json b/packages/apollo-server-testing/package.json index 20bb1d1fb12..818f758fa75 100644 --- a/packages/apollo-server-testing/package.json +++ b/packages/apollo-server-testing/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-testing", - "version": "2.12.0", + "version": "2.11.0", "description": "Test utils for apollo-server", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -30,6 +30,6 @@ "apollo-server-types": "file:../apollo-server-types" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-server-types/package.json b/packages/apollo-server-types/package.json index 2ffbe4d973a..3e3b096eb9e 100644 --- a/packages/apollo-server-types/package.json +++ b/packages/apollo-server-types/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server-types", - "version": "0.3.1", + "version": "0.3.0", "description": "Apollo Server shared types", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -16,6 +16,6 @@ "apollo-server-env": "file:../apollo-server-env" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-server-types/src/index.ts b/packages/apollo-server-types/src/index.ts index 969872aca49..68b52128309 100644 --- a/packages/apollo-server-types/src/index.ts +++ b/packages/apollo-server-types/src/index.ts @@ -19,7 +19,6 @@ export type WithRequired = T & Required>; type Mutable = { -readonly [P in keyof T]: T[P] }; export interface GraphQLServiceContext { - logger: Logger; schema: GraphQLSchema; schemaHash: string; engine: { @@ -63,8 +62,6 @@ export interface GraphQLRequestContext> { readonly request: GraphQLRequest; readonly response?: GraphQLResponse; - logger: Logger; - readonly context: TContext; readonly cache: KeyValueCache; @@ -99,7 +96,10 @@ export type ValidationRule = (context: ValidationContext) => ASTVisitor; export class InvalidGraphQLRequestError extends GraphQLError {} export type GraphQLExecutor> = ( - requestContext: GraphQLRequestContextExecutionDidStart, + requestContext: WithRequired< + GraphQLRequestContext, + 'document' | 'operationName' | 'operation' | 'queryHash' + >, ) => ValueOrPromise; export type GraphQLExecutionResult = { @@ -107,54 +107,3 @@ export type GraphQLExecutionResult = { errors?: ReadonlyArray; extensions?: Record; }; - -export type Logger = { - // Ordered from least-severe to most-severe. - debug(message?: any): void; - info(message?: any): void; - warn(message?: any): void; - error(message?: any): void; -} - -export type GraphQLRequestContextParsingDidStart = - WithRequired, - | 'metrics' - | 'source' - | 'queryHash' - >; -export type GraphQLRequestContextValidationDidStart = - GraphQLRequestContextParsingDidStart & - WithRequired, - | 'document' - >; -export type GraphQLRequestContextDidResolveOperation = - GraphQLRequestContextValidationDidStart & - WithRequired, - | 'operation' - | 'operationName' - >; -export type GraphQLRequestContextDidEncounterErrors = - WithRequired, - | 'metrics' - | 'errors' - >; -export type GraphQLRequestContextResponseForOperation = - WithRequired, - | 'metrics' - | 'source' - | 'document' - | 'operation' - | 'operationName' - >; -export type GraphQLRequestContextExecutionDidStart = - GraphQLRequestContextParsingDidStart & - WithRequired, - | 'document' - | 'operation' - | 'operationName' - >; -export type GraphQLRequestContextWillSendResponse = - WithRequired, - | 'metrics' - | 'response' - >; diff --git a/packages/apollo-server/package.json b/packages/apollo-server/package.json index f5e258f6961..a30247b5344 100644 --- a/packages/apollo-server/package.json +++ b/packages/apollo-server/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server", - "version": "2.12.0", + "version": "2.11.0", "description": "Production ready GraphQL Server", "author": "opensource@apollographql.com", "main": "dist/index.js", @@ -28,6 +28,6 @@ "graphql-tools": "^4.0.0" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-server/src/index.ts b/packages/apollo-server/src/index.ts index 19e25bafc77..3911fbd1d3a 100644 --- a/packages/apollo-server/src/index.ts +++ b/packages/apollo-server/src/index.ts @@ -97,8 +97,6 @@ export class ApolloServer extends ApolloServerBase { // object, so we have to create it. const app = express(); - app.disable('x-powered-by'); - // provide generous values for the getting started experience super.applyMiddleware({ app, diff --git a/packages/apollo-tracing/package.json b/packages/apollo-tracing/package.json index 9261935ae0e..316a21eacd0 100644 --- a/packages/apollo-tracing/package.json +++ b/packages/apollo-tracing/package.json @@ -1,6 +1,6 @@ { "name": "apollo-tracing", - "version": "0.9.1", + "version": "0.9.0", "description": "Collect and expose trace data for GraphQL requests", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -15,6 +15,6 @@ "graphql-extensions": "file:../graphql-extensions" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/graphql-extensions/package.json b/packages/graphql-extensions/package.json index 92266750f03..5f472ba16ad 100644 --- a/packages/graphql-extensions/package.json +++ b/packages/graphql-extensions/package.json @@ -1,6 +1,6 @@ { "name": "graphql-extensions", - "version": "0.11.1", + "version": "0.11.0", "description": "Add extensions to GraphQL servers", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -20,6 +20,6 @@ "apollo-server-types": "file:../apollo-server-types" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/renovate.json5 b/renovate.json5 index f22ff3bbc07..4c2abb7dd6f 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -32,10 +32,6 @@ "packageNames": ["@koa/cors"], "allowedVersions": "<3" }, - { - "packageNames": ["log4js"], - "allowedVersions": "<5" - }, { "packageNames": ["hapi", "@types/hapi"], "allowedVersions": "<18" diff --git a/types/loglevel-debug/index.d.ts b/types/loglevel-debug/index.d.ts new file mode 100644 index 00000000000..36cf04af657 --- /dev/null +++ b/types/loglevel-debug/index.d.ts @@ -0,0 +1,4 @@ +import { Logger } from 'loglevel'; +declare module 'loglevel-debug' { + export default function(logger: Logger): any; +}