From d34d34d82e268efb7ee0fa6fa545f6b4b426b1d2 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Thu, 6 Feb 2020 14:18:50 +0200 Subject: [PATCH 01/27] changelog: Kick-off v2.11.0. --- CHANGELOG.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61056ff8b1c..318c75f70ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,7 @@ The version headers in this history reflect the versions of Apollo Server itself - [__CHANGELOG for `@apollo/gateway`__](https://github.com/apollographql/apollo-server/blob/master/packages/apollo-gateway/CHANGELOG.md) - [__CHANGELOG for `@apollo/federation`__](https://github.com/apollographql/apollo-server/blob/master/packages/apollo-federation/CHANGELOG.md) -### vNEXT - -> The changes noted within this `vNEXT` section have not been released yet. New PRs and commits which introduce changes should include an entry in this `vNEXT` section as part of their development. When a release is being prepared, a new header will be (manually) created below and the the appropriate changes within that release will be moved into the new section. +### v2.11.0 - _Nothing yet! Stay tuned._ From 1ea1bdef5cab9e0f9ed11c77e87a588a6fbe010b Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Fri, 7 Feb 2020 17:03:54 +0200 Subject: [PATCH 02/27] breaking: Change `RemoteGraphQLDataSource` `didReceiveResponse` (#3743) Existing users who have NOT sub-classed `RemoteGraphQLDataSource` with their own implementation of `didReceiveResponse` and have NOT similarly provided a `didReceiveResponse` option to its constructor are unaffected by this change. **All others must adjust their implementations of `didReceiveResponse`!** This commit makes breaking changes to the existing expectations, signature and behavior of the optionally overridden `didReceiveResponse` hook in the `RemoteGraphQLDataSource` class. This change is intended to better align the signature of `didReceiveResponse` with similar life-cycle hooks on the `RemoteGraphQLDataSource`, such as the `willSendRequest` method. Furthermore, this change helps prepare for an upcoming Apollo Gateway feature which allows it to operate in APQ (automated persisted query) mode with downstream services. Specifically, this commit changes `didReceiveResponse` in the following ways: - **Will now receive [`GraphQLRequestContext`] as the only parameter.** Previously, it received three parameters: `res`, `req` and `context`, where `res` and `req` were the HTTP `Response` and `Request` respectively. These values are still available on the `GraphQLRequestContext` object, though they are located on the `http` property (of each), as is the case in most other Apollo Server constructs. Not only does this more consistently match the pattern received by `RemoteGraphQLDataSource`'s `willSendRequest` but it also matches the patterns from `ApolloServerPlugin`'s life-cycle hooks. See the referenced type definition for details on `GraphQLRequestContext`. - **Sub-classes no longer needs to call base class methods.** Previously, it was either necessary to call `this.parseBody` or `super.didReceiveResponse` merely to parse the incoming response body into JSON and return it. This is no longer necessary. If you desire changing the parsing behavior, override the (already public) `parseBody` method appropriately. - **It no longer has a default implementation** Previously, the `didReceiveResponse` method had baked-in response parsing in a way that made it more difficult to use the method for its primary purpose, which was to offer implementers the ability to lift transport-specific properties (e.g. headers) onto the overall request context. For example, using a response header's value as a contributor to a header that eventually gets returned by the gateway. Because there is no longer an implementation, existing users no longer need to call `super.didReceiveResponse`. - **TypeScript: The `TContext` is now the only type argument.** Previously, this method accepted two type arguments. Now, it only requires a single type argument, the `TContext`. [`GraphQLRequestContext`]: https://github.com/apollographql/apollo-server/blob/2562d096/packages/apollo-server-types/src/index.ts#L61-L65 --- docs/source/api/apollo-gateway.mdx | 32 +++++++---- docs/source/federation/implementing.md | 9 +-- packages/apollo-gateway/CHANGELOG.md | 2 + .../datasources/RemoteGraphQLDataSource.ts | 41 ++++++++------ .../__tests__/RemoteGraphQLDataSource.test.ts | 55 +++++++++++++++---- 5 files changed, 96 insertions(+), 43 deletions(-) diff --git a/docs/source/api/apollo-gateway.mdx b/docs/source/api/apollo-gateway.mdx index 1fc688e8a4a..c4b1ba79531 100644 --- a/docs/source/api/apollo-gateway.mdx +++ b/docs/source/api/apollo-gateway.mdx @@ -157,24 +157,34 @@ These methods are described in detail below. }); ``` - ### `didReceiveResponse`: `(response: Response, request: Request, context: TContext) => Promise` + ### `didReceiveResponse`: `(requestContext: {request: GraphQLRequest, response: GraphQLResponse, context: Record}) => Promise` Override this method to customize the gateway's behavior after completing - a fetch to the implementing service. The method takes the original `request`, the implementing service's `response`, and the current `context` as parameters, allowing you to modify any combination of the context and the final result of the fetch. - - This method must return an object that matches the structure of a - [`GraphQLExecutionResult`](https://github.com/apollographql/apollo-server/blob/master/packages/apollo-server-types/src/index.ts#L105-L109) (i.e., it - should include `data`, `errors`, and/or `extensions` fields). - - ```js{2-9} + a fetch to the implementing service. The method receives an object (a + [`GraphQLRequestContext`](https://github.com/apollographql/apollo-server/blob/2562d096/packages/apollo-server-types/src/index.ts#L61-L92)) + containing the `request` (a `GraphQLRequest`), the implementing service's + `response` (a `GraphQLResponse`), and the current `context` as parameters. + This allows modification of the context and the final result of the fetch. + + The `http` property on the `request` and `response` objects will contain + additional HTTP-specific properties, like `headers`. + + Any implementation of this method must return an object that matches the + structure of a [`GraphQLResponse`](https://github.com/apollographql/apollo-server/blob/2562d096/packages/apollo-server-types/src/index.ts#L43-L48) + (i.e., it should include `http`, `data`, `errors`, and/or `extensions` + fields). If no modifications are necessary, simply return the original + `response`. + + ```javascript class CookieDataSource extends RemoteGraphQLDataSource { - didReceiveResponse(response, request, context) { - const body = super.didReceiveResponse(response, request, context); + didReceiveResponse({ response, request, context }) { const cookie = request.http.headers.get('Cookie'); if (cookie) { context.responseCookies.push(cookie); } - return body; + + // Return the response back, even when unchanged. + return response; } } ``` diff --git a/docs/source/federation/implementing.md b/docs/source/federation/implementing.md index 8be104093d5..4b131bc11b8 100644 --- a/docs/source/federation/implementing.md +++ b/docs/source/federation/implementing.md @@ -303,14 +303,15 @@ const { ApolloGateway, RemoteGraphQLDataSource } = require('@apollo/gateway'); class DataSourceWithServerId extends RemoteGraphQLDataSource { // highlight-start - async didReceiveResponse(response, request, context) { - const body = await super.didReceiveResponse(response, request, context); + async didReceiveResponse({ response, request, context }) { // Parse the Server-Id header and add it to the array on context - const serverId = response.headers.get('Server-Id'); + const serverId = response.http.headers.get('Server-Id'); if (serverId) { context.serverIds.push(serverId); } - return body; + + // Return the response, even when unchanged. + return response; } // highlight-end } diff --git a/packages/apollo-gateway/CHANGELOG.md b/packages/apollo-gateway/CHANGELOG.md index d3b6800bb07..3c2d4c876b0 100644 --- a/packages/apollo-gateway/CHANGELOG.md +++ b/packages/apollo-gateway/CHANGELOG.md @@ -4,6 +4,8 @@ > The changes noted within this `vNEXT` section have not been released yet. New PRs and commits which introduce changes should include an entry in this `vNEXT` section as part of their development. When a release is being prepared, a new header will be (manually) created below and the the appropriate changes within that release will be moved into the new section. +- __BREAKING__: The behavior and signature of `RemoteGraphQLDataSource`'s `didReceiveResponse` method has been changed. No changes are necessary _unless_ your implementation has overridden the default behavior of this method by either extending the class and overriding the method or by providing `didReceiveResponse` as a parameter to the `RemoteGraphQLDataSource`'s constructor options. Implementations which have provided their own `didReceiveResponse` using either of these methods should view the PR linked here for details on what has changed. [PR #3743](https://github.com/apollographql/apollo-server/pull/3743) + ## v0.12.0 > [See complete versioning details.](https://github.com/apollographql/apollo-server/commit/9c0aa1e661ccc2c5a1471b781102637dd47e21b1) diff --git a/packages/apollo-gateway/src/datasources/RemoteGraphQLDataSource.ts b/packages/apollo-gateway/src/datasources/RemoteGraphQLDataSource.ts index 2ad9da71072..edcb6005596 100644 --- a/packages/apollo-gateway/src/datasources/RemoteGraphQLDataSource.ts +++ b/packages/apollo-gateway/src/datasources/RemoteGraphQLDataSource.ts @@ -2,6 +2,7 @@ import { GraphQLRequestContext, GraphQLResponse, ValueOrPromise, + GraphQLRequest, } from 'apollo-server-types'; import { ApolloError, @@ -59,14 +60,19 @@ export class RemoteGraphQLDataSource implements GraphQLDataSource { const httpRequest = new Request(request.http.url, options); + const respond = (response: GraphQLResponse, request: GraphQLRequest) => + typeof this.didReceiveResponse === "function" + ? this.didReceiveResponse({ response, request, context }) + : response; + try { const httpResponse = await fetch(httpRequest); - const body = await this.didReceiveResponse( - httpResponse, - httpRequest, - context, - ); + if (!httpResponse.ok) { + throw await this.errorFromResponse(httpResponse); + } + + const body = await this.parseBody(httpResponse, httpRequest, context); if (!isObject(body)) { throw new Error(`Expected JSON response body, but received: ${body}`); @@ -77,7 +83,7 @@ export class RemoteGraphQLDataSource implements GraphQLDataSource { http: httpResponse, }; - return response; + return respond(response, request); } catch (error) { this.didEncounterError(error, httpRequest); throw error; @@ -91,23 +97,22 @@ export class RemoteGraphQLDataSource implements GraphQLDataSource { >, ): ValueOrPromise; - public async didReceiveResponse( - response: Response, - _request: Request, - _context?: TContext, - ): Promise { - if (response.ok) { - return (this.parseBody(response) as any) as Promise; - } else { - throw await this.errorFromResponse(response); - } - } + public didReceiveResponse?( + requestContext: Required, + 'request' | 'response' | 'context'> + >, + ): ValueOrPromise; public didEncounterError(error: Error, _request: Request) { throw error; } - public parseBody(response: Response): Promise { + public parseBody( + response: Response, + _request?: Request, + _context?: TContext, + ): Promise { const contentType = response.headers.get('Content-Type'); if (contentType && contentType.startsWith('application/json')) { return response.json(); diff --git a/packages/apollo-gateway/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts b/packages/apollo-gateway/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts index ed20ff31607..9a84fccafbe 100644 --- a/packages/apollo-gateway/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +++ b/packages/apollo-gateway/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts @@ -7,7 +7,8 @@ import { } from 'apollo-server-errors'; import { RemoteGraphQLDataSource } from '../RemoteGraphQLDataSource'; -import { Headers, Response, Request } from 'apollo-server-env'; +import { Headers } from 'apollo-server-env'; +import { GraphQLRequestContext } from 'apollo-server-types'; beforeEach(() => { fetch.mockReset(); @@ -126,17 +127,19 @@ describe('didReceiveResponse', () => { class MyDataSource extends RemoteGraphQLDataSource { url = 'https://api.example.com/foo'; - async didReceiveResponse( - response: Response, - request: Request, - context: TContext, - ): Promise { - const body = await super.didReceiveResponse(response, request, context); - const surrogateKeys = request.headers.get('surrogate-keys'); + didReceiveResponse({ + request, + response, + }: Required, + 'request' | 'response' | 'context' + >>) { + const surrogateKeys = + request.http && request.http.headers.get('surrogate-keys'); if (surrogateKeys) { - (context as any).surrogateKeys.push(...surrogateKeys.split(' ')); + context.surrogateKeys.push(...surrogateKeys.split(' ')); } - return body; + return response; } } @@ -160,6 +163,38 @@ describe('didReceiveResponse', () => { expect(context).toEqual({ surrogateKeys: ['abc', 'def'] }); }); + + it('is only called once', async () => { + class MyDataSource extends RemoteGraphQLDataSource { + url = 'https://api.example.com/foo'; + + didReceiveResponse({ + response, + }: Required, + 'request' | 'response' | 'context' + >>) { + return response; + } + } + + const DataSource = new MyDataSource(); + const spyDidReceiveResponse = + jest.spyOn(DataSource, 'didReceiveResponse'); + + fetch.mockJSONResponseOnce({ data: { me: 'james' } }); + + await DataSource.process({ + request: { + query: '{ me { name } }', + variables: { id: '1' }, + }, + context: {}, + }); + + expect(spyDidReceiveResponse).toHaveBeenCalledTimes(1); + + }); }); describe('error handling', () => { From 7a8826af1cb4f9345c927a51cea3ed510243add5 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Fri, 7 Feb 2020 17:19:58 +0200 Subject: [PATCH 03/27] gateway: Allow use of APQ when querying downstream services. (#3744) * gateway: Allow use of APQ when querying downstream services. This introduces support to Apollo Gateway which can leverage [Automated Persisted Queries] (APQ) when communicating with downstream implementing services in a federated architecture. In an identical way as APQ saves bytes on the wire (read: network) in a traditional handshake between a client and the server, this implements that behavior in the internal communication between federated servers and the gateway that fronts them. This is accomplished by first attempting to utilizing a SHA-256 hex hash (consistently, 32 bytes) of the operation which is destined for the downstream server, rather than the complete, typically much larger query itself. In the event that the downstream server supports APQ (Apollo Server does by default, unless it's been disabled), the downstream server will check its APQ registry for the full operation body. If it has it, it will use that cached body to process the request and return the results. If it does not find the query in its existing registry, it will return a message to the gateway that the query is not found in its registry, and the gateway will re-transmit the request with the full operation payload. On receipt of this full query, the downstream server will cache the operation for future requests (keyed by the SHA-256 hash), and return the expected result without further delay. This means that once a server has warmed up its APQ registry with repeated operations, subsequent network chatter will be greatly reduced. Furthermore, as noted in the attached documentation, the APQ registry can be backed by a distributed store, such as Memcached or Redis, allowing multiple downstream GraphQL servers to share the same cache, and persist it across restarts. By default, APQ behavior is disabled on `RemoteGraphQLDataSource`. To enable it, the `apq` property should be set to true. Future versions of the `RemoteGraphQLDataSource` could negotiate this capability on their own, but this does not attempt to make that negotiation right now. [Automated Persisted Queries]: https://www.apollographql.com/docs/apollo-server/performance/apq/ * tests: De-compose `toHaveFetched` in preparation for similar matchers. * tests: Introduce `toHaveFetchNth` with to test critical order of fetches. It's plausible that we should change every existing `toHaveFetch` to use this matcher which enforces order. Though it also seems plausible that custom matchers aren't as flexible as they need to be in practice, since in addition to the need to use Jest-built in methods (like the `nth`-call matchers) there are other specific usages of this which are just surfacing now (with APQ) that could be tested less precisely using `expect.objectContaining()`, rather than testing concerns which are not really necessary (like matching the hash). If this matcher still supported partial matches then this would be possible. However, since we're serializing the body into an `Request` before matching it (which I'm not sure why we do and there is no comment to indicate why) this isn't possible as Jest's matchers cannot survive that serialization. * tests: Make `Matcher` declaration merges match new Jest definitions. Without this change, every single usage of our `Matcher` is represented as a type error since Jest has changed their own implementation of `Matcher` to introduce a new generic type argument. It's too bad that this didn't fail tests at the time that that Jest package was updated! * Re-jigger `RemoteGraphQLDataSource`'s `process` with new `sendRequest` method. This introduces a new private `sendRequest` method that handles existing behavior which existed within `RemoteGraphQLDataSource`'s `process`. It should be a no-op change. An upcoming commit will make multiple requests to downstream services in its course of attempting APQ negotiation and this should facilitate that change and avoid repetition of logic. --- packages/apollo-gateway/CHANGELOG.md | 1 + .../src/__tests__/matchers/toCallService.ts | 2 +- .../matchers/toHaveBeenCalledBefore.ts | 2 +- .../src/__tests__/matchers/toHaveFetched.ts | 45 ++- .../src/__tests__/matchers/toMatchAST.ts | 2 +- .../datasources/RemoteGraphQLDataSource.ts | 96 ++++++- .../__tests__/RemoteGraphQLDataSource.test.ts | 268 ++++++++++++++++-- 7 files changed, 368 insertions(+), 48 deletions(-) diff --git a/packages/apollo-gateway/CHANGELOG.md b/packages/apollo-gateway/CHANGELOG.md index 3c2d4c876b0..4dcf5dcb9e0 100644 --- a/packages/apollo-gateway/CHANGELOG.md +++ b/packages/apollo-gateway/CHANGELOG.md @@ -5,6 +5,7 @@ > The changes noted within this `vNEXT` section have not been released yet. New PRs and commits which introduce changes should include an entry in this `vNEXT` section as part of their development. When a release is being prepared, a new header will be (manually) created below and the the appropriate changes within that release will be moved into the new section. - __BREAKING__: The behavior and signature of `RemoteGraphQLDataSource`'s `didReceiveResponse` method has been changed. No changes are necessary _unless_ your implementation has overridden the default behavior of this method by either extending the class and overriding the method or by providing `didReceiveResponse` as a parameter to the `RemoteGraphQLDataSource`'s constructor options. Implementations which have provided their own `didReceiveResponse` using either of these methods should view the PR linked here for details on what has changed. [PR #3743](https://github.com/apollographql/apollo-server/pull/3743) +- __NEW__: Setting the `apq` option to `true` on the `RemoteGraphQLDataSource` will enable the use of [automated persisted queries (APQ)](https://www.apollographql.com/docs/apollo-server/performance/apq/) when sending queries to downstream services. Depending on the complexity of queries sent to downstream services, this technique can greatly reduce the size of the payloads being transmitted over the network. Downstream implementing services must also support APQ functionality to participate in this feature (Apollo Server does by default unless it has been explicitly disabled). As with normal APQ behavior, a downstream server must have received and registered a query once before it will be able to serve an APQ request. [#3744](https://github.com/apollographql/apollo-server/pull/3744) ## v0.12.0 diff --git a/packages/apollo-gateway/src/__tests__/matchers/toCallService.ts b/packages/apollo-gateway/src/__tests__/matchers/toCallService.ts index 7d526620db1..1640861fa62 100644 --- a/packages/apollo-gateway/src/__tests__/matchers/toCallService.ts +++ b/packages/apollo-gateway/src/__tests__/matchers/toCallService.ts @@ -6,7 +6,7 @@ const prettyFormat = require('pretty-format'); declare global { namespace jest { - interface Matchers { + interface Matchers { toCallService(service: string): R; } } diff --git a/packages/apollo-gateway/src/__tests__/matchers/toHaveBeenCalledBefore.ts b/packages/apollo-gateway/src/__tests__/matchers/toHaveBeenCalledBefore.ts index 017111c5b70..1b5f86c64f3 100644 --- a/packages/apollo-gateway/src/__tests__/matchers/toHaveBeenCalledBefore.ts +++ b/packages/apollo-gateway/src/__tests__/matchers/toHaveBeenCalledBefore.ts @@ -3,7 +3,7 @@ export {}; declare global { namespace jest { - interface Matchers { + interface Matchers { toHaveBeenCalledBefore(spy: SpyInstance): R; } } diff --git a/packages/apollo-gateway/src/__tests__/matchers/toHaveFetched.ts b/packages/apollo-gateway/src/__tests__/matchers/toHaveFetched.ts index 89c95781ce7..4ebbcc24ba7 100644 --- a/packages/apollo-gateway/src/__tests__/matchers/toHaveFetched.ts +++ b/packages/apollo-gateway/src/__tests__/matchers/toHaveFetched.ts @@ -5,18 +5,16 @@ import { Request, RequestInit, Headers } from 'apollo-server-env'; export {}; declare global { namespace jest { - interface Matchers { + interface Matchers { toHaveFetched(spy: SpyInstance): R; } } } -function toHaveFetched( - this: jest.MatcherUtils, - fetch: jest.SpyInstance, - request: RequestInit & { url: string }, -): { message(): string; pass: boolean } { - let headers = new Headers(); +type ExtendedRequest = RequestInit & { url: string }; + +function prepareHttpRequest(request: ExtendedRequest): Request { + const headers = new Headers(); headers.set('Content-Type', 'application/json'); if (request.headers) { for (let name in request.headers) { @@ -30,8 +28,15 @@ function toHaveFetched( body: JSON.stringify(request.body), }; - const httpRequest = new Request(request.url, options); + return new Request(request.url, options); +} +function toHaveFetched( + this: jest.MatcherUtils, + fetch: jest.SpyInstance, + request: ExtendedRequest, +): { message(): string; pass: boolean } { + const httpRequest = prepareHttpRequest(request); let pass = false; let message = () => ''; try { @@ -47,6 +52,30 @@ function toHaveFetched( }; } +function toHaveFetchedNth( + this: jest.MatcherUtils, + fetch: jest.SpyInstance, + nthCall: number, + request: ExtendedRequest, +): { message(): string; pass: boolean } { + const httpRequest = prepareHttpRequest(request); + let pass = false; + let message = () => ''; + try { + expect(fetch).toHaveBeenNthCalledWith(nthCall, httpRequest); + pass = true; + } catch (e) { + message = () => e.message; + } + + return { + message, + pass, + }; +} + + expect.extend({ toHaveFetched, + toHaveFetchedNth, }); diff --git a/packages/apollo-gateway/src/__tests__/matchers/toMatchAST.ts b/packages/apollo-gateway/src/__tests__/matchers/toMatchAST.ts index 42ceb93dd32..8df057e7c96 100644 --- a/packages/apollo-gateway/src/__tests__/matchers/toMatchAST.ts +++ b/packages/apollo-gateway/src/__tests__/matchers/toMatchAST.ts @@ -3,7 +3,7 @@ const diff = require('jest-diff'); declare global { namespace jest { - interface Matchers { + interface Matchers { toMatchAST(expected: ASTNode): R; } } diff --git a/packages/apollo-gateway/src/datasources/RemoteGraphQLDataSource.ts b/packages/apollo-gateway/src/datasources/RemoteGraphQLDataSource.ts index edcb6005596..b9198aecf35 100644 --- a/packages/apollo-gateway/src/datasources/RemoteGraphQLDataSource.ts +++ b/packages/apollo-gateway/src/datasources/RemoteGraphQLDataSource.ts @@ -12,12 +12,12 @@ import { import { fetch, Request, - RequestInit, Headers, Response, } from 'apollo-server-env'; import { isObject } from '../utilities/predicates'; import { GraphQLDataSource } from './types'; +import createSHA from 'apollo-server-core/dist/utils/createSHA'; export class RemoteGraphQLDataSource implements GraphQLDataSource { constructor( @@ -32,6 +32,26 @@ export class RemoteGraphQLDataSource implements GraphQLDataSource { url!: string; + /** + * Whether the downstream request should be made with automated persisted + * query (APQ) behavior enabled. + * + * @remarks When enabled, the request to the downstream service will first be + * attempted using a SHA-256 hash of the operation rather than including the + * operation itself. If the downstream server supports APQ and has this + * operation registered in its APQ storage, it will be able to complete the + * request without the entirety of the operation document being transmitted. + * + * In the event that the downstream service is unaware of the operation, it + * will respond with an `PersistedQueryNotFound` error and it will be resent + * with the full operation body for fulfillment. + * + * Generally speaking, when the downstream server is processing similar + * operations repeatedly, APQ can offer substantial network savings in terms + * of bytes transmitted over the wire between gateways and downstream servers. + */ + apq: boolean = false; + async process({ request, context, @@ -52,19 +72,77 @@ export class RemoteGraphQLDataSource implements GraphQLDataSource { await this.willSendRequest({ request, context }); } - const { http, ...graphqlRequest } = request; - const options: RequestInit = { - ...http, - body: JSON.stringify(graphqlRequest), - }; + if (!request.query) { + throw new Error("Missing query"); + } + + const apqHash = createSHA('sha256') + .update(request.query) + .digest('hex'); - const httpRequest = new Request(request.http.url, options); + const { query, ...requestWithoutQuery } = request; const respond = (response: GraphQLResponse, request: GraphQLRequest) => typeof this.didReceiveResponse === "function" ? this.didReceiveResponse({ response, request, context }) : response; + if (this.apq) { + // Take the original extensions and extend them with + // the necessary "extensions" for APQ handshaking. + requestWithoutQuery.extensions = { + ...request.extensions, + persistedQuery: { + version: 1, + sha256Hash: apqHash, + }, + }; + + const apqOptimisticResponse = + await this.sendRequest(requestWithoutQuery, context); + + // If we didn't receive notice to retry with APQ, then let's + // assume this is the best result we'll get and return it! + if ( + !apqOptimisticResponse.errors || + !apqOptimisticResponse.errors.find(error => + error.message === 'PersistedQueryNotFound') + ) { + return respond(apqOptimisticResponse, requestWithoutQuery); + } + } + + // If APQ was enabled, we'll run the same request again, but add in the + // previously omitted `query`. If APQ was NOT enabled, this is the first + // request (non-APQ, all the way). + const requestWithQuery: GraphQLRequest = { + query, + ...requestWithoutQuery, + }; + const response = await this.sendRequest(requestWithQuery, context); + return respond(response, requestWithQuery); + } + + private async sendRequest( + request: GraphQLRequest, + context: TContext, + ): Promise { + + // This would represent an internal programming error since this shouldn't + // be possible in the way that this method is invoked right now. + if (!request.http) { + throw new Error("Internal error: Only 'http' requests are supported.") + } + + // We don't want to serialize the `http` properties into the body that is + // being transmitted. Instead, we want those to be used to indicate what + // we're accessing (e.g. url) and what we access it with (e.g. headers). + const { http, ...requestWithoutHttp } = request; + const httpRequest = new Request(http.url, { + ...http, + body: JSON.stringify(requestWithoutHttp), + }); + try { const httpResponse = await fetch(httpRequest); @@ -78,12 +156,10 @@ export class RemoteGraphQLDataSource implements GraphQLDataSource { throw new Error(`Expected JSON response body, but received: ${body}`); } - const response: GraphQLResponse = { + return { ...body, http: httpResponse, }; - - return respond(response, request); } catch (error) { this.didEncounterError(error, httpRequest); throw error; diff --git a/packages/apollo-gateway/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts b/packages/apollo-gateway/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts index 9a84fccafbe..ff5309dc000 100644 --- a/packages/apollo-gateway/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +++ b/packages/apollo-gateway/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts @@ -15,44 +15,225 @@ beforeEach(() => { }); describe('constructing requests', () => { - it('stringifies a request with a query', async () => { - const DataSource = new RemoteGraphQLDataSource({ - url: 'https://api.example.com/foo', + describe('without APQ', () => { + it('stringifies a request with a query', async () => { + const DataSource = new RemoteGraphQLDataSource({ + url: 'https://api.example.com/foo', + apq: false, + }); + + fetch.mockJSONResponseOnce({ data: { me: 'james' } }); + + const { data } = await DataSource.process({ + request: { query: '{ me { name } }' }, + context: {}, + }); + + expect(data).toEqual({ me: 'james' }); + expect(fetch).toBeCalledTimes(1); + expect(fetch).toHaveFetched({ + url: 'https://api.example.com/foo', + body: { query: '{ me { name } }' }, + }); }); - fetch.mockJSONResponseOnce({ data: { me: 'james' } }); + it('passes variables', async () => { + const DataSource = new RemoteGraphQLDataSource({ + url: 'https://api.example.com/foo', + apq: false, + }); - const { data } = await DataSource.process({ - request: { query: '{ me { name } }' }, - }); + fetch.mockJSONResponseOnce({ data: { me: 'james' } }); - expect(data).toEqual({ me: 'james' }); - expect(fetch).toBeCalledTimes(1); - expect(fetch).toHaveFetched({ - url: 'https://api.example.com/foo', - body: { query: '{ me { name } }' }, + const { data } = await DataSource.process({ + request: { + query: '{ me { name } }', + variables: { id: '1' }, + }, + context: {}, + }); + + expect(data).toEqual({ me: 'james' }); + expect(fetch).toBeCalledTimes(1); + expect(fetch).toHaveFetched({ + url: 'https://api.example.com/foo', + body: { query: '{ me { name } }', variables: { id: '1' } }, + }); }); }); - it('passes variables', async () => { - const DataSource = new RemoteGraphQLDataSource({ - url: 'https://api.example.com/foo', - }); + describe('with APQ', () => { + // When changing this, adjust the SHA-256 hash below as well. + const query = '{ me { name } }'; - fetch.mockJSONResponseOnce({ data: { me: 'james' } }); + // This is a SHA-256 hash of `query` above. + const sha256Hash = + "b8d9506e34c83b0e53c2aa463624fcea354713bc38f95276e6f0bd893ffb5b88"; - const { data } = await DataSource.process({ - request: { - query: '{ me { name } }', - variables: { id: '1' }, - }, + describe('miss', () => { + const apqNotFoundResponse = { + "errors": [ + { + "message": "PersistedQueryNotFound", + "extensions": { + "code": "PERSISTED_QUERY_NOT_FOUND", + "exception": { + "stacktrace": ["PersistedQueryNotFoundError: PersistedQueryNotFound"] + } + } + } + ] + }; + + it('stringifies a request with a query', async () => { + const DataSource = new RemoteGraphQLDataSource({ + url: 'https://api.example.com/foo', + apq: true, + }); + + fetch.mockJSONResponseOnce(apqNotFoundResponse); + fetch.mockJSONResponseOnce({ data: { me: 'james' } }); + + const { data } = await DataSource.process({ + request: { query }, + context: {}, + }); + + expect(data).toEqual({ me: 'james' }); + expect(fetch).toBeCalledTimes(2); + expect(fetch).toHaveFetchedNth(1, { + url: 'https://api.example.com/foo', + body: { + extensions: { + persistedQuery: { + version: 1, + sha256Hash, + } + } + }, + }); + expect(fetch).toHaveFetchedNth(2, { + url: 'https://api.example.com/foo', + body: { + query, + extensions: { + persistedQuery: { + version: 1, + sha256Hash, + } + } + }, + }); + }); + + it('passes variables', async () => { + const DataSource = new RemoteGraphQLDataSource({ + url: 'https://api.example.com/foo', + apq: true, + }); + + fetch.mockJSONResponseOnce(apqNotFoundResponse); + fetch.mockJSONResponseOnce({ data: { me: 'james' } }); + + const { data } = await DataSource.process({ + request: { + query, + variables: { id: '1' }, + }, + context: {}, + }); + + expect(data).toEqual({ me: 'james' }); + expect(fetch).toBeCalledTimes(2); + expect(fetch).toHaveFetchedNth(1, { + url: 'https://api.example.com/foo', + body: { + variables: { id: '1' }, + extensions: { + persistedQuery: { + version: 1, + sha256Hash, + } + } + }, + }); + + expect(fetch).toHaveFetchedNth(2, { + url: 'https://api.example.com/foo', + body: { + query, + variables: { id: '1' }, + extensions: { + persistedQuery: { + version: 1, + sha256Hash, + } + } + }, + }); + }); }); - expect(data).toEqual({ me: 'james' }); - expect(fetch).toBeCalledTimes(1); - expect(fetch).toHaveFetched({ - url: 'https://api.example.com/foo', - body: { query: '{ me { name } }', variables: { id: '1' } }, + describe('hit', () => { + it('stringifies a request with a query', async () => { + const DataSource = new RemoteGraphQLDataSource({ + url: 'https://api.example.com/foo', + apq: true, + }); + + fetch.mockJSONResponseOnce({ data: { me: 'james' } }); + + const { data } = await DataSource.process({ + request: { query }, + context: {}, + }); + + expect(data).toEqual({ me: 'james' }); + expect(fetch).toBeCalledTimes(1); + expect(fetch).toHaveFetched({ + url: 'https://api.example.com/foo', + body: { + extensions: { + persistedQuery: { + version: 1, + sha256Hash, + } + } + }, + }); + }); + + it('passes variables', async () => { + const DataSource = new RemoteGraphQLDataSource({ + url: 'https://api.example.com/foo', + apq: true, + }); + + fetch.mockJSONResponseOnce({ data: { me: 'james' } }); + + const { data } = await DataSource.process({ + request: { + query, + variables: { id: '1' }, + }, + context: {}, + }); + + expect(data).toEqual({ me: 'james' }); + expect(fetch).toBeCalledTimes(1); + expect(fetch).toHaveFetched({ + url: 'https://api.example.com/foo', + body: { + variables: { id: '1' }, + extensions: { + persistedQuery: { + version: 1, + sha256Hash, + } + } + }, + }); + }); }); }); }); @@ -195,6 +376,39 @@ describe('didReceiveResponse', () => { expect(spyDidReceiveResponse).toHaveBeenCalledTimes(1); }); + + // APQ makes two requests, so make sure only one calls the response hook. + it('is only called once when apq is enabled', async () => { + class MyDataSource extends RemoteGraphQLDataSource { + url = 'https://api.example.com/foo'; + apq = true; + + didReceiveResponse({ + response, + }: Required, + 'request' | 'response' | 'context' + >>) { + return response; + } + } + + const DataSource = new MyDataSource(); + const spyDidReceiveResponse = jest.spyOn(DataSource, 'didReceiveResponse'); + + fetch.mockJSONResponseOnce({ data: { me: 'james' } }); + + await DataSource.process({ + request: { + query: '{ me { name } }', + variables: { id: '1' }, + }, + context: {}, + }); + + expect(spyDidReceiveResponse).toHaveBeenCalledTimes(1); + + }); }); describe('error handling', () => { From eaf92a14c8cc34e277e4391140c7139fc3d012d9 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Fri, 7 Feb 2020 17:23:09 +0200 Subject: [PATCH 04/27] docs: Add description of #3744's `apq` option to gateway API docs. --- docs/source/api/apollo-gateway.mdx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/source/api/apollo-gateway.mdx b/docs/source/api/apollo-gateway.mdx index c4b1ba79531..3d0a6ca49dc 100644 --- a/docs/source/api/apollo-gateway.mdx +++ b/docs/source/api/apollo-gateway.mdx @@ -40,6 +40,18 @@ example of using `ApolloGateway`, see [Implementing a federated graph](/federati }); ``` + * `apq`: `Boolean` + + When enabled the gateway will attempt to use [automated persisted queries (APQ)](https://www.apollographql.com/docs/apollo-server/performance/apq/) + when sending queries to downstream services. Depending on the complexity + of queries sent to downstream services, this technique can greatly reduce + the size of the payloads being transmitted over the network. Downstream + implementing services must also support APQ functionality to participate + in this feature (Apollo Server does by default unless it has been + explicitly disabled). As with typical APQ behavior, a downstream server + must have received and registered a query once before it will be able to + serve an APQ request. + * `buildService`: `(service: ServiceDefinition) => GraphQLDataSource` Define this function to customize your gateway's data transport to some or From 2a3e2943056c2ff75a3f631e7c55a2d1cecab9b2 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Fri, 7 Feb 2020 17:24:40 +0200 Subject: [PATCH 05/27] Release - @apollo/federation@0.13.0-alpha.0 - @apollo/gateway@0.13.0-alpha.0 --- packages/apollo-federation/package.json | 2 +- packages/apollo-gateway/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/apollo-federation/package.json b/packages/apollo-federation/package.json index 9c7b3107a77..32a34234b55 100644 --- a/packages/apollo-federation/package.json +++ b/packages/apollo-federation/package.json @@ -1,6 +1,6 @@ { "name": "@apollo/federation", - "version": "0.12.0", + "version": "0.13.0-alpha.0", "description": "Apollo Federation Utilities", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-gateway/package.json b/packages/apollo-gateway/package.json index 1ad9228516e..dfeee9494a3 100644 --- a/packages/apollo-gateway/package.json +++ b/packages/apollo-gateway/package.json @@ -1,6 +1,6 @@ { "name": "@apollo/gateway", - "version": "0.12.0", + "version": "0.13.0-alpha.0", "description": "Apollo Gateway", "author": "opensource@apollographql.com", "main": "dist/index.js", From 4131c58b98cd28a865805e7b7e48aa34485139a3 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Fri, 7 Feb 2020 17:26:20 +0200 Subject: [PATCH 06/27] changelog: Update gateway changelog header to reflect prerelease version. --- packages/apollo-gateway/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/apollo-gateway/CHANGELOG.md b/packages/apollo-gateway/CHANGELOG.md index 4dcf5dcb9e0..4bfe96a9fa6 100644 --- a/packages/apollo-gateway/CHANGELOG.md +++ b/packages/apollo-gateway/CHANGELOG.md @@ -1,6 +1,6 @@ # CHANGELOG for `@apollo/gateway` -## vNEXT +## 0.13.0 (pre-release; `@next` tag) > The changes noted within this `vNEXT` section have not been released yet. New PRs and commits which introduce changes should include an entry in this `vNEXT` section as part of their development. When a release is being prepared, a new header will be (manually) created below and the the appropriate changes within that release will be moved into the new section. From 10ac19fd1a0e4f383b1207ecf97afc22036c8d53 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Tue, 4 Feb 2020 12:52:21 +0200 Subject: [PATCH 07/27] no-op: Rename `test` to `it` for consistency. --- .../apollo-engine-reporting/src/__tests__/extension.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/apollo-engine-reporting/src/__tests__/extension.test.ts b/packages/apollo-engine-reporting/src/__tests__/extension.test.ts index 64dd9f1ed55..e4d2aa0782c 100644 --- a/packages/apollo-engine-reporting/src/__tests__/extension.test.ts +++ b/packages/apollo-engine-reporting/src/__tests__/extension.test.ts @@ -15,7 +15,7 @@ import { InMemoryLRUCache } from 'apollo-server-caching'; import { AddTraceArgs } from '../agent'; import { Trace } from 'apollo-engine-reporting-protobuf'; -test('trace construction', async () => { +it('trace construction', async () => { const typeDefs = ` type User { id: Int From 671afe670416e253be243a73075c551bd6f440a1 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Tue, 4 Feb 2020 12:52:37 +0200 Subject: [PATCH 08/27] tests: Remove unnecessary casting to `any`. --- .../apollo-engine-reporting/src/__tests__/extension.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/apollo-engine-reporting/src/__tests__/extension.test.ts b/packages/apollo-engine-reporting/src/__tests__/extension.test.ts index e4d2aa0782c..f896ee3199f 100644 --- a/packages/apollo-engine-reporting/src/__tests__/extension.test.ts +++ b/packages/apollo-engine-reporting/src/__tests__/extension.test.ts @@ -67,7 +67,7 @@ it('trace construction', async () => { ); const stack = new GraphQLExtensionStack([reportingExtension]); const requestDidEnd = stack.requestDidStart({ - request: new Request('http://localhost:123/foo') as any, + request: new Request('http://localhost:123/foo'), queryString: query, requestContext: { request: { From cf6c7ad94ec1c48c6636b2e81b87d573ffea07cb Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Tue, 4 Feb 2020 12:55:24 +0200 Subject: [PATCH 09/27] tests: Improve typing and pass `queryHash` through to `addTrace` mock. --- .../apollo-engine-reporting/src/__tests__/extension.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/apollo-engine-reporting/src/__tests__/extension.test.ts b/packages/apollo-engine-reporting/src/__tests__/extension.test.ts index f896ee3199f..8576a95da5b 100644 --- a/packages/apollo-engine-reporting/src/__tests__/extension.test.ts +++ b/packages/apollo-engine-reporting/src/__tests__/extension.test.ts @@ -55,9 +55,9 @@ it('trace construction', async () => { addMockFunctionsToSchema({ schema }); enableGraphQLExtensions(schema); - const traces: Array = []; - async function addTrace({ trace, operationName, schemaHash }: AddTraceArgs) { - traces.push({ schemaHash, operationName, trace }); + const traces: Array = []; + async function addTrace(args: AddTraceArgs) { + traces.push(args); } const reportingExtension = new EngineReportingExtension( From e97260d3303fd9b6c5733268160e2c5e8729c8a4 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Tue, 4 Feb 2020 13:03:25 +0200 Subject: [PATCH 10/27] tests: types: Fix type errors within tests. --- .../src/__tests__/extension.test.ts | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/apollo-engine-reporting/src/__tests__/extension.test.ts b/packages/apollo-engine-reporting/src/__tests__/extension.test.ts index 8576a95da5b..14b50c73cfd 100644 --- a/packages/apollo-engine-reporting/src/__tests__/extension.test.ts +++ b/packages/apollo-engine-reporting/src/__tests__/extension.test.ts @@ -145,11 +145,17 @@ describe('check variableJson output for sendVariableValues all/none type', () => }); it('Case 4: Check behavior for invalid inputs', () => { - expect(makeTraceDetails(variables, { none: false })).toEqual( + expect(makeTraceDetails(variables, + // @ts-ignore Testing untyped usage; only `{ none: true }` is legal. + { none: false } + )).toEqual( nonFilteredOutput, ); - expect(makeTraceDetails(variables, { all: false })).toEqual(filteredOutput); + expect(makeTraceDetails(variables, + // @ts-ignore Testing untyped usage; only `{ all: true }` is legal. + { all: false } + )).toEqual(filteredOutput); }); }); @@ -292,8 +298,12 @@ describe('variableJson output for sendVariableValues transform: custom function }); describe('Catch circular reference error during JSON.stringify', () => { - const circularReference = {}; - circularReference['this'] = circularReference; + interface SelfCircular { + self?: SelfCircular; + } + + const circularReference: SelfCircular = {}; + circularReference['self'] = circularReference; const circularVariables = { bad: circularReference, @@ -324,8 +334,10 @@ const headersOutput = { name: new Trace.HTTP.Values({ value: ['value'] }) }; describe('tests for the sendHeaders reporting option', () => { it('sendHeaders defaults to hiding all', () => { const http = makeTestHTTP(); - // sendHeaders: null is not a valid TS input, but check the output anyways - makeHTTPRequestHeaders(http, headers, null); + makeHTTPRequestHeaders(http, headers, + // @ts-ignore: `null` is not a valid type; check output on invalid input. + null + ); expect(http.requestHeaders).toEqual({}); makeHTTPRequestHeaders(http, headers, undefined); expect(http.requestHeaders).toEqual({}); @@ -345,11 +357,17 @@ describe('tests for the sendHeaders reporting option', () => { it('invalid inputs for sendHeaders.all and sendHeaders.none', () => { const httpSafelist = makeTestHTTP(); - makeHTTPRequestHeaders(httpSafelist, headers, { none: false }); + makeHTTPRequestHeaders(httpSafelist, headers, + // @ts-ignore Testing untyped usage; only `{ none: true }` is legal. + { none: false } + ); expect(httpSafelist.requestHeaders).toEqual(headersOutput); const httpBlocklist = makeTestHTTP(); - makeHTTPRequestHeaders(httpBlocklist, headers, { all: false }); + makeHTTPRequestHeaders(httpBlocklist, headers, + // @ts-ignore Testing untyped usage; only `{ all: true }` is legal. + { all: false } + ); expect(httpBlocklist.requestHeaders).toEqual({}); }); From e405526ffc7d709f95fcd7488de53ee5a2b9267c Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Fri, 14 Feb 2020 15:55:31 +0200 Subject: [PATCH 11/27] Update CHANGELOG.md version headings prior to bumping version. --- packages/apollo-federation/CHANGELOG.md | 6 ++---- packages/apollo-gateway/CHANGELOG.md | 4 +--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/apollo-federation/CHANGELOG.md b/packages/apollo-federation/CHANGELOG.md index 4871fd283f7..20e6d92ff16 100644 --- a/packages/apollo-federation/CHANGELOG.md +++ b/packages/apollo-federation/CHANGELOG.md @@ -1,10 +1,8 @@ # CHANGELOG for `@apollo/federation` -## vNEXT +## 0.13.1 (pre-release; `@next` tag) -> The changes noted within this `vNEXT` section have not been released yet. New PRs and commits which introduce changes should include an entry in this `vNEXT` section as part of their development. When a release is being prepared, a new header will be (manually) created below and the the appropriate changes within that release will be moved into the new section. - -- _Nothing yet! Stay tuned._ +- Only changes in the similarly versioned `@apollo/gateway` package. ## v0.12.1 diff --git a/packages/apollo-gateway/CHANGELOG.md b/packages/apollo-gateway/CHANGELOG.md index fa81f2ccab9..1557777f2f8 100644 --- a/packages/apollo-gateway/CHANGELOG.md +++ b/packages/apollo-gateway/CHANGELOG.md @@ -1,8 +1,6 @@ # CHANGELOG for `@apollo/gateway` -## 0.13.0 (pre-release; `@next` tag) - -> The changes noted within this `vNEXT` section have not been released yet. New PRs and commits which introduce changes should include an entry in this `vNEXT` section as part of their development. When a release is being prepared, a new header will be (manually) created below and the the appropriate changes within that release will be moved into the new section. +## 0.13.1 (pre-release; `@next` tag) - __BREAKING__: The behavior and signature of `RemoteGraphQLDataSource`'s `didReceiveResponse` method has been changed. No changes are necessary _unless_ your implementation has overridden the default behavior of this method by either extending the class and overriding the method or by providing `didReceiveResponse` as a parameter to the `RemoteGraphQLDataSource`'s constructor options. Implementations which have provided their own `didReceiveResponse` using either of these methods should view the PR linked here for details on what has changed. [PR #3743](https://github.com/apollographql/apollo-server/pull/3743) - __NEW__: Setting the `apq` option to `true` on the `RemoteGraphQLDataSource` will enable the use of [automated persisted queries (APQ)](https://www.apollographql.com/docs/apollo-server/performance/apq/) when sending queries to downstream services. Depending on the complexity of queries sent to downstream services, this technique can greatly reduce the size of the payloads being transmitted over the network. Downstream implementing services must also support APQ functionality to participate in this feature (Apollo Server does by default unless it has been explicitly disabled). As with normal APQ behavior, a downstream server must have received and registered a query once before it will be able to serve an APQ request. [#3744](https://github.com/apollographql/apollo-server/pull/3744) From df345c6a4f202c32a2e61a19ccccf79118450d19 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Fri, 14 Feb 2020 17:25:10 +0200 Subject: [PATCH 12/27] Release - @apollo/federation@0.13.1-alpha.0 - @apollo/gateway@0.13.1-alpha.0 --- packages/apollo-federation/package.json | 2 +- packages/apollo-gateway/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/apollo-federation/package.json b/packages/apollo-federation/package.json index 32a34234b55..4ea06847d33 100644 --- a/packages/apollo-federation/package.json +++ b/packages/apollo-federation/package.json @@ -1,6 +1,6 @@ { "name": "@apollo/federation", - "version": "0.13.0-alpha.0", + "version": "0.13.1-alpha.0", "description": "Apollo Federation Utilities", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-gateway/package.json b/packages/apollo-gateway/package.json index dfeee9494a3..976bd40ef22 100644 --- a/packages/apollo-gateway/package.json +++ b/packages/apollo-gateway/package.json @@ -1,6 +1,6 @@ { "name": "@apollo/gateway", - "version": "0.13.0-alpha.0", + "version": "0.13.1-alpha.0", "description": "Apollo Gateway", "author": "opensource@apollographql.com", "main": "dist/index.js", From c48c0a3c3ebde6bf6a9d78b8cf9c98f36d073226 Mon Sep 17 00:00:00 2001 From: Trevor Scheer Date: Fri, 14 Feb 2020 17:44:03 -0800 Subject: [PATCH 13/27] =?UTF-8?q?Gateway(experimental):=20compress=20downs?= =?UTF-8?q?tream=20requests=20via=20generat=E2=80=A6=20(#3791)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For large queries (esp. ones that leverage many nested fragments), queries to downstream services can blow up astronomically because the gateway expands everything during a downstream request. i.e. we've seen a ~700 line query become over 200k lines due to this expansion. This commit has the gateway build fragments on the fly for the selection sets that it encounters and reuses them whenever possible. This is an initial and simple implementation, but with tangible wins. This is currently being landed as an experimental feature that is disabled by default. To enable, simply add to your gateway config: experimental_autoFragmentization: true --- packages/apollo-gateway/CHANGELOG.md | 3 + packages/apollo-gateway/src/QueryPlan.ts | 1 + .../src/__tests__/buildQueryPlan.test.ts | 333 +++++++++++++++++- .../integration/networkRequests.test.ts | 12 +- .../src/__tests__/integration/nockMocks.ts | 2 +- packages/apollo-gateway/src/buildQueryPlan.ts | 130 ++++++- .../apollo-gateway/src/executeQueryPlan.ts | 92 ++--- packages/apollo-gateway/src/index.ts | 7 +- .../queryPlanSerializer.ts | 17 + 9 files changed, 525 insertions(+), 72 deletions(-) diff --git a/packages/apollo-gateway/CHANGELOG.md b/packages/apollo-gateway/CHANGELOG.md index 1557777f2f8..ebcacd8e9fa 100644 --- a/packages/apollo-gateway/CHANGELOG.md +++ b/packages/apollo-gateway/CHANGELOG.md @@ -2,8 +2,11 @@ ## 0.13.1 (pre-release; `@next` tag) +> The changes noted within this `vNEXT` section have not been released yet. New PRs and commits which introduce changes should include an entry in this `vNEXT` section as part of their development. When a release is being prepared, a new header will be (manually) created below and the the appropriate changes within that release will be moved into the new section. + - __BREAKING__: The behavior and signature of `RemoteGraphQLDataSource`'s `didReceiveResponse` method has been changed. No changes are necessary _unless_ your implementation has overridden the default behavior of this method by either extending the class and overriding the method or by providing `didReceiveResponse` as a parameter to the `RemoteGraphQLDataSource`'s constructor options. Implementations which have provided their own `didReceiveResponse` using either of these methods should view the PR linked here for details on what has changed. [PR #3743](https://github.com/apollographql/apollo-server/pull/3743) - __NEW__: Setting the `apq` option to `true` on the `RemoteGraphQLDataSource` will enable the use of [automated persisted queries (APQ)](https://www.apollographql.com/docs/apollo-server/performance/apq/) when sending queries to downstream services. Depending on the complexity of queries sent to downstream services, this technique can greatly reduce the size of the payloads being transmitted over the network. Downstream implementing services must also support APQ functionality to participate in this feature (Apollo Server does by default unless it has been explicitly disabled). As with normal APQ behavior, a downstream server must have received and registered a query once before it will be able to serve an APQ request. [#3744](https://github.com/apollographql/apollo-server/pull/3744) +- __NEW__: Experimental feature: compress downstream requests via generated fragments [#3791](https://github.com/apollographql/apollo-server/pull/3791) This feature enables the gateway to generate fragments for queries to downstream services in order to minimize bytes over the wire and parse time. This can be enabled via the gateway config by setting `experimental_autoFragmentization: true`. It is currently disabled by default. ## v0.12.1 diff --git a/packages/apollo-gateway/src/QueryPlan.ts b/packages/apollo-gateway/src/QueryPlan.ts index 4a8efe6a2f5..0899b2a7653 100644 --- a/packages/apollo-gateway/src/QueryPlan.ts +++ b/packages/apollo-gateway/src/QueryPlan.ts @@ -41,6 +41,7 @@ export interface FetchNode { selectionSet: SelectionSetNode; variableUsages?: { [name: string]: VariableDefinitionNode }; requires?: SelectionSetNode; + internalFragments: Set; } export interface FlattenNode { kind: 'Flatten'; diff --git a/packages/apollo-gateway/src/__tests__/buildQueryPlan.test.ts b/packages/apollo-gateway/src/__tests__/buildQueryPlan.test.ts index dd5c67cd085..71b1449a398 100644 --- a/packages/apollo-gateway/src/__tests__/buildQueryPlan.test.ts +++ b/packages/apollo-gateway/src/__tests__/buildQueryPlan.test.ts @@ -856,25 +856,336 @@ describe('buildQueryPlan', () => { const queryPlan = buildQueryPlan(buildOperationContext(schema, query)); expect(queryPlan).toMatchInlineSnapshot(` - QueryPlan { - Fetch(service: "product") { - { - product(upc: "") { - __typename - ... on Book { + QueryPlan { + Fetch(service: "product") { + { + product(upc: "") { + __typename + ... on Book { + details { + country + } + } + ... on Furniture { + details { + country + } + } + } + } + }, + } + `); + }); + + describe(`experimental compression to downstream services`, () => { + it(`should generate fragments internally to downstream requests`, () => { + const query = gql` + query { + topReviews { + body + author + product { + name + price details { country } } - ... on Furniture { + } + } + `; + + const queryPlan = buildQueryPlan( + buildOperationContext(schema, query, undefined), + { autoFragmentization: true }, + ); + + expect(queryPlan).toMatchInlineSnapshot(` + QueryPlan { + Sequence { + Fetch(service: "reviews") { + { + topReviews { + ...__QueryPlanFragment_1__ + } + } + fragment __QueryPlanFragment_1__ on Review { + body + author + product { + ...__QueryPlanFragment_0__ + } + } + fragment __QueryPlanFragment_0__ on Product { + __typename + ... on Book { + __typename + isbn + } + ... on Furniture { + __typename + upc + } + } + }, + Parallel { + Sequence { + Flatten(path: "topReviews.@.product") { + Fetch(service: "books") { + { + ... on Book { + __typename + isbn + } + } => + { + ... on Book { + __typename + isbn + title + year + } + } + }, + }, + Flatten(path: "topReviews.@.product") { + Fetch(service: "product") { + { + ... on Book { + __typename + isbn + title + year + } + } => + { + ... on Book { + name + } + } + }, + }, + }, + Flatten(path: "topReviews.@.product") { + Fetch(service: "product") { + { + ... on Furniture { + __typename + upc + } + ... on Book { + __typename + isbn + } + } => + { + ... on Furniture { + name + price + details { + country + } + } + ... on Book { + price + details { + country + } + } + } + }, + }, + }, + }, + } + `); + }); + + it(`shouldn't generate fragments for selection sets of length 2 or less`, () => { + const query = gql` + query { + topReviews { + body + author + } + } + `; + + const queryPlan = buildQueryPlan( + buildOperationContext(schema, query, undefined), + { autoFragmentization: true }, + ); + + expect(queryPlan).toMatchInlineSnapshot(` + QueryPlan { + Fetch(service: "reviews") { + { + topReviews { + body + author + } + } + }, + } + `); + }); + + it(`should generate fragments for selection sets of length 3 or greater`, () => { + const query = gql` + query { + topReviews { + id + body + author + } + } + `; + + const queryPlan = buildQueryPlan( + buildOperationContext(schema, query, undefined), + { autoFragmentization: true }, + ); + + expect(queryPlan).toMatchInlineSnapshot(` + QueryPlan { + Fetch(service: "reviews") { + { + topReviews { + ...__QueryPlanFragment_0__ + } + } + fragment __QueryPlanFragment_0__ on Review { + id + body + author + } + }, + } + `); + }); + + it(`should generate fragments correctly when aliases are used`, () => { + const query = gql` + query { + reviews: topReviews { + content: body + author + product { + name + cost: price details { - country + origin: country } } } } - }, - } - `); + `; + + const queryPlan = buildQueryPlan( + buildOperationContext(schema, query, undefined), + { autoFragmentization: true }, + ); + + expect(queryPlan).toMatchInlineSnapshot(` + QueryPlan { + Sequence { + Fetch(service: "reviews") { + { + reviews: topReviews { + ...__QueryPlanFragment_1__ + } + } + fragment __QueryPlanFragment_1__ on Review { + content: body + author + product { + ...__QueryPlanFragment_0__ + } + } + fragment __QueryPlanFragment_0__ on Product { + __typename + ... on Book { + __typename + isbn + } + ... on Furniture { + __typename + upc + } + } + }, + Parallel { + Sequence { + Flatten(path: "reviews.@.product") { + Fetch(service: "books") { + { + ... on Book { + __typename + isbn + } + } => + { + ... on Book { + __typename + isbn + title + year + } + } + }, + }, + Flatten(path: "reviews.@.product") { + Fetch(service: "product") { + { + ... on Book { + __typename + isbn + title + year + } + } => + { + ... on Book { + name + } + } + }, + }, + }, + Flatten(path: "reviews.@.product") { + Fetch(service: "product") { + { + ... on Furniture { + __typename + upc + } + ... on Book { + __typename + isbn + } + } => + { + ... on Furniture { + name + cost: price + details { + origin: country + } + } + ... on Book { + cost: price + details { + origin: country + } + } + } + }, + }, + }, + }, + } + `); + }); }); }); diff --git a/packages/apollo-gateway/src/__tests__/integration/networkRequests.test.ts b/packages/apollo-gateway/src/__tests__/integration/networkRequests.test.ts index 9fe0f861a17..718e60137d0 100644 --- a/packages/apollo-gateway/src/__tests__/integration/networkRequests.test.ts +++ b/packages/apollo-gateway/src/__tests__/integration/networkRequests.test.ts @@ -87,7 +87,7 @@ it('Extracts service definitions from remote storage', async () => { implementingServiceLocations: [ { name: federatedServiceName, - path: `${storageSecret}/current/v1/implementing-services/${federatedServiceName}/${implementingServicePath}.json`, + path: `${storageSecret}/current/v1/implementing-services/${federatedServiceName}/${implementingServicePath}`, }, ], }); @@ -169,7 +169,7 @@ it('Rollsback to a previous schema when triggered', async () => { implementingServiceLocations: [ { name: federatedServiceName, - path: `${storageSecret}/current/v1/implementing-services/${federatedServiceName}/${implementingServicePath1}.json`, + path: `${storageSecret}/current/v1/implementing-services/${federatedServiceName}/${implementingServicePath1}`, }, ], }); @@ -205,7 +205,7 @@ it('Rollsback to a previous schema when triggered', async () => { implementingServiceLocations: [ { name: federatedServiceName, - path: `${storageSecret}/current/v1/implementing-services/${federatedServiceName}/${implementingServicePath2}.json`, + path: `${storageSecret}/current/v1/implementing-services/${federatedServiceName}/${implementingServicePath2}`, }, ], }); @@ -241,7 +241,7 @@ it('Rollsback to a previous schema when triggered', async () => { implementingServiceLocations: [ { name: federatedServiceName, - path: `${storageSecret}/current/v1/implementing-services/${federatedServiceName}/${implementingServicePath1}.json`, + path: `${storageSecret}/current/v1/implementing-services/${federatedServiceName}/${implementingServicePath1}`, }, ], }); @@ -271,7 +271,7 @@ it('Rollsback to a previous schema when triggered', async () => { // 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, 10)); + await new Promise(resolve => setTimeout(resolve, 100)); jest.useFakeTimers(); expect(onChange.mock.calls.length).toBe(1); @@ -279,7 +279,7 @@ it('Rollsback to a previous schema when triggered', async () => { jest.advanceTimersByTime(10000); jest.useRealTimers(); - await new Promise(resolve => setTimeout(resolve, 10)); + await new Promise(resolve => setTimeout(resolve, 100)); jest.useFakeTimers(); 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 65b4f755106..d0595353c45 100644 --- a/packages/apollo-gateway/src/__tests__/integration/nockMocks.ts +++ b/packages/apollo-gateway/src/__tests__/integration/nockMocks.ts @@ -43,7 +43,7 @@ export const mockGetImplementingServices = ({ federatedServiceName: string; }) => nock('https://storage.googleapis.com:443').get( - `/engine-partial-schema-prod/${storageSecret}/current/v1/implementing-services/${federatedServiceName}/${implementingServicePath}.json`, + `/engine-partial-schema-prod/${storageSecret}/current/v1/implementing-services/${federatedServiceName}/${implementingServicePath}`, ); // get raw-partial-schema, using received composition-config diff --git a/packages/apollo-gateway/src/buildQueryPlan.ts b/packages/apollo-gateway/src/buildQueryPlan.ts index 1dcfe048e92..f6264408585 100644 --- a/packages/apollo-gateway/src/buildQueryPlan.ts +++ b/packages/apollo-gateway/src/buildQueryPlan.ts @@ -57,8 +57,15 @@ const typenameField = { }, }; -export function buildQueryPlan(operationContext: OperationContext): QueryPlan { - const context = buildQueryPlanningContext(operationContext); +interface BuildQueryPlanOptions { + autoFragmentization: boolean; +} + +export function buildQueryPlan( + operationContext: OperationContext, + options: BuildQueryPlanOptions = { autoFragmentization: false }, +): QueryPlan { + const context = buildQueryPlanningContext(operationContext, options); if (context.operation.operation === 'subscription') { throw new GraphQLError( @@ -110,7 +117,8 @@ function executionNodeForGroup( group.requiredFields && group.requiredFields.length > 0 ? selectionSetFromFieldSet(group.requiredFields) : undefined, - variableUsages: context.getVariableUsages(selectionSet), + variableUsages: context.getVariableUsages(selectionSet, group.internalFragments), + internalFragments: group.internalFragments }; const node: PlanNode = @@ -545,17 +553,83 @@ function completeField( parentGroup.otherDependentGroups.push(...subGroup.dependentGroups); + let definition: FragmentDefinitionNode; + let selectionSet = selectionSetFromFieldSet(subGroup.fields, returnType); + + if (context.autoFragmentization && subGroup.fields.length > 2) { + ({ definition, selectionSet } = getInternalFragment( + selectionSet, + returnType, + context, + )); + parentGroup.internalFragments.add(definition); + } + + // "Hoist" internalFragments of the subGroup into the parentGroup so all + // fragments can be included in the final request for the root FetchGroup + subGroup.internalFragments.forEach(fragment => { + parentGroup.internalFragments.add(fragment); + }); + return { scope, fieldNode: { ...fieldNode, - selectionSet: selectionSetFromFieldSet(subGroup.fields, returnType), + selectionSet, }, fieldDef, }; } } +function getInternalFragment( + selectionSet: SelectionSetNode, + returnType: GraphQLCompositeType, + context: QueryPlanningContext +) { + const key = JSON.stringify(selectionSet); + if (!context.internalFragments.has(key)) { + const name = `__QueryPlanFragment_${context.internalFragmentCount++}__`; + + const definition: FragmentDefinitionNode = { + kind: Kind.FRAGMENT_DEFINITION, + name: { + kind: Kind.NAME, + value: name, + }, + typeCondition: { + kind: Kind.NAMED_TYPE, + name: { + kind: Kind.NAME, + value: returnType.name, + }, + }, + selectionSet, + }; + + const fragmentSelection: SelectionSetNode = { + kind: Kind.SELECTION_SET, + selections: [ + { + kind: Kind.FRAGMENT_SPREAD, + name: { + kind: Kind.NAME, + value: name, + }, + }, + ], + }; + + context.internalFragments.set(key, { + name, + definition, + selectionSet: fragmentSelection, + }); + } + + return context.internalFragments.get(key)!; +} + function collectFields( context: QueryPlanningContext, scope: Scope, @@ -652,6 +726,7 @@ class FetchGroup { constructor( public readonly serviceName: string, public readonly fields: FieldSet = [], + public readonly internalFragments: Set = new Set() ) {} requiredFields: FieldSet = []; @@ -735,15 +810,30 @@ export function buildOperationContext( return { schema, operation, fragments }; } -export function buildQueryPlanningContext({ - operation, - schema, - fragments, -}: OperationContext): QueryPlanningContext { - return new QueryPlanningContext(schema, operation, fragments); +export function buildQueryPlanningContext( + { operation, schema, fragments }: OperationContext, + options: BuildQueryPlanOptions, +): QueryPlanningContext { + return new QueryPlanningContext( + schema, + operation, + fragments, + options.autoFragmentization, + ); } export class QueryPlanningContext { + public internalFragments: Map< + string, + { + name: string; + definition: FragmentDefinitionNode; + selectionSet: SelectionSetNode; + } + > = new Map(); + + public internalFragmentCount = 0; + protected variableDefinitions: { [name: string]: VariableDefinitionNode; }; @@ -752,6 +842,7 @@ export class QueryPlanningContext { public readonly schema: GraphQLSchema, public readonly operation: OperationDefinitionNode, public readonly fragments: FragmentMap, + public readonly autoFragmentization: boolean, ) { this.variableDefinitions = Object.create(null); visit(operation, { @@ -784,13 +875,26 @@ export class QueryPlanningContext { return isAbstractType(type) ? this.schema.getPossibleTypes(type) : [type]; } - getVariableUsages(selectionSet: SelectionSetNode) { + getVariableUsages( + selectionSet: SelectionSetNode, + fragments: Set, + ) { const usages: { [name: string]: VariableDefinitionNode; } = Object.create(null); - visit(selectionSet, { - Variable: node => { + // Construct a document of the selection set and fragment definitions so we + // can visit them, adding all variable usages to the `usages` object. + const document: DocumentNode = { + kind: Kind.DOCUMENT, + definitions: [ + { kind: Kind.OPERATION_DEFINITION, selectionSet, operation: 'query' }, + ...Array.from(fragments), + ], + }; + + visit(document, { + Variable: (node) => { usages[node.name.value] = this.variableDefinitions[node.name.value]; }, }); diff --git a/packages/apollo-gateway/src/executeQueryPlan.ts b/packages/apollo-gateway/src/executeQueryPlan.ts index 3abc94a23d9..cb499dfdbd0 100644 --- a/packages/apollo-gateway/src/executeQueryPlan.ts +++ b/packages/apollo-gateway/src/executeQueryPlan.ts @@ -7,7 +7,6 @@ import { execute, GraphQLError, Kind, - OperationDefinitionNode, OperationTypeNode, print, SelectionSetNode, @@ -15,6 +14,7 @@ import { VariableDefinitionNode, GraphQLFieldResolver, stripIgnoredCharacters, + DocumentNode, } from 'graphql'; import { Trace, google } from 'apollo-engine-reporting-protobuf'; import { GraphQLDataSource } from './datasources/types'; @@ -287,7 +287,7 @@ async function executeFetch( async function sendOperation( context: ExecutionContext, - operation: OperationDefinitionNode, + operation: DocumentNode, variables: Record, ): Promise { const source = stripIgnoredCharacters(print(operation)); @@ -488,65 +488,77 @@ function mapFetchNodeToVariableDefinitions( function operationForRootFetch( fetch: FetchNode, operation: OperationTypeNode = 'query', -): OperationDefinitionNode { +): DocumentNode { return { - kind: Kind.OPERATION_DEFINITION, - operation, - selectionSet: fetch.selectionSet, - variableDefinitions: mapFetchNodeToVariableDefinitions(fetch), + kind: Kind.DOCUMENT, + definitions: [ + { + kind: Kind.OPERATION_DEFINITION, + operation, + selectionSet: fetch.selectionSet, + variableDefinitions: mapFetchNodeToVariableDefinitions(fetch), + }, + ...fetch.internalFragments, + ], }; } -function operationForEntitiesFetch(fetch: FetchNode): OperationDefinitionNode { +function operationForEntitiesFetch(fetch: FetchNode): DocumentNode { const representationsVariable = { kind: Kind.VARIABLE, name: { kind: Kind.NAME, value: 'representations' }, }; return { - kind: Kind.OPERATION_DEFINITION, - operation: 'query', - variableDefinitions: ([ + kind: Kind.DOCUMENT, + definitions: [ { - kind: Kind.VARIABLE_DEFINITION, - variable: representationsVariable, - type: { - kind: Kind.NON_NULL_TYPE, - type: { - kind: Kind.LIST_TYPE, + kind: Kind.OPERATION_DEFINITION, + operation: 'query', + variableDefinitions: ([ + { + kind: Kind.VARIABLE_DEFINITION, + variable: representationsVariable, type: { kind: Kind.NON_NULL_TYPE, type: { - kind: Kind.NAMED_TYPE, - name: { kind: Kind.NAME, value: '_Any' }, + kind: Kind.LIST_TYPE, + type: { + kind: Kind.NON_NULL_TYPE, + type: { + kind: Kind.NAMED_TYPE, + name: { kind: Kind.NAME, value: '_Any' }, + }, + }, }, }, }, - }, - }, - ] as VariableDefinitionNode[]).concat( - mapFetchNodeToVariableDefinitions(fetch), - ), - selectionSet: { - kind: Kind.SELECTION_SET, - selections: [ - { - kind: Kind.FIELD, - name: { kind: Kind.NAME, value: '_entities' }, - arguments: [ + ] as VariableDefinitionNode[]).concat( + mapFetchNodeToVariableDefinitions(fetch), + ), + selectionSet: { + kind: Kind.SELECTION_SET, + selections: [ { - kind: Kind.ARGUMENT, - name: { - kind: Kind.NAME, - value: representationsVariable.name.value, - }, - value: representationsVariable, + kind: Kind.FIELD, + name: { kind: Kind.NAME, value: '_entities' }, + arguments: [ + { + kind: Kind.ARGUMENT, + name: { + kind: Kind.NAME, + value: representationsVariable.name.value, + }, + value: representationsVariable, + }, + ], + selectionSet: fetch.selectionSet, }, ], - selectionSet: fetch.selectionSet, }, - ], - }, + }, + ...fetch.internalFragments + ], }; } diff --git a/packages/apollo-gateway/src/index.ts b/packages/apollo-gateway/src/index.ts index 03aba950b42..e5f020c18f5 100644 --- a/packages/apollo-gateway/src/index.ts +++ b/packages/apollo-gateway/src/index.ts @@ -59,6 +59,7 @@ interface GatewayConfigBase { experimental_didUpdateComposition?: Experimental_DidUpdateCompositionCallback; experimental_pollInterval?: number; experimental_approximateQueryPlanStoreMiB?: number; + experimental_autoFragmentization?: boolean; } interface RemoteGatewayConfig extends GatewayConfigBase { @@ -512,7 +513,11 @@ export class ApolloGateway implements GraphQLService { } if (!queryPlan) { - queryPlan = buildQueryPlan(operationContext); + queryPlan = buildQueryPlan(operationContext, { + autoFragmentization: Boolean( + this.config.experimental_autoFragmentization, + ), + }); if (this.queryPlanStore) { // The underlying cache store behind the `documentStore` returns a // `Promise` which is resolved (or rejected), eventually, based on the diff --git a/packages/apollo-gateway/src/snapshotSerializers/queryPlanSerializer.ts b/packages/apollo-gateway/src/snapshotSerializers/queryPlanSerializer.ts index ef858ec5221..5a2db1fdba4 100644 --- a/packages/apollo-gateway/src/snapshotSerializers/queryPlanSerializer.ts +++ b/packages/apollo-gateway/src/snapshotSerializers/queryPlanSerializer.ts @@ -71,6 +71,23 @@ function printNode( ) + config.spacingOuter + indentation + + (node.internalFragments.size > 0 + ? ' ' + + Array.from(node.internalFragments) + .map(fragment => + printer( + fragment, + config, + indentationNext, + depth, + refs, + printer, + ), + ) + .join(`\n${indentationNext}`) + + config.spacingOuter + + indentation + : '') + '}'; break; case 'Flatten': From ff47978d004d5c39242ae6b3bf771c7dab70602f Mon Sep 17 00:00:00 2001 From: Trevor Scheer Date: Fri, 14 Feb 2020 17:45:27 -0800 Subject: [PATCH 14/27] Release - @apollo/gateway@0.13.2-alpha.0 --- packages/apollo-gateway/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/apollo-gateway/package.json b/packages/apollo-gateway/package.json index 976bd40ef22..5cc8c02033a 100644 --- a/packages/apollo-gateway/package.json +++ b/packages/apollo-gateway/package.json @@ -1,6 +1,6 @@ { "name": "@apollo/gateway", - "version": "0.13.1-alpha.0", + "version": "0.13.2-alpha.0", "description": "Apollo Gateway", "author": "opensource@apollographql.com", "main": "dist/index.js", From 9420aa049471f0c5e8df62a0924fd94678508a2a Mon Sep 17 00:00:00 2001 From: Trevor Scheer Date: Wed, 19 Feb 2020 16:34:15 -0800 Subject: [PATCH 15/27] Gateway: introduce `make-fetch-happen` (#3783) * Introduce `make-fetch-happen` for GCS requests * Implement in memory cache manager for make-fetch-happen * Provide local typings for `make-fetch-happen` * Skip node v6 tests for federation and gateway packages --- package-lock.json | 431 +++++++++++++++--- packages/apollo-federation/jest.config.js | 15 +- packages/apollo-gateway/CHANGELOG.md | 1 + packages/apollo-gateway/jest.config.js | 12 +- packages/apollo-gateway/package.json | 1 + packages/apollo-gateway/src/cache.ts | 55 +++ packages/apollo-gateway/src/cachedFetcher.ts | 69 --- packages/apollo-gateway/src/index.ts | 13 +- .../src/loadServicesFromStorage.ts | 46 +- .../apollo-gateway/src/make-fetch-happen.d.ts | 53 +++ 10 files changed, 520 insertions(+), 176 deletions(-) create mode 100644 packages/apollo-gateway/src/cache.ts delete mode 100644 packages/apollo-gateway/src/cachedFetcher.ts create mode 100644 packages/apollo-gateway/src/make-fetch-happen.d.ts diff --git a/package-lock.json b/package-lock.json index ba433ee3513..1c9e7d2ccf5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "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" }, "dependencies": { @@ -44,6 +45,182 @@ "requires": { "@types/node": "*" } + }, + "agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==" + }, + "agentkeepalive": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.1.0.tgz", + "integrity": "sha512-CW/n1wxF8RpEuuiq6Vbn9S8m0VSYDMnZESqaJ6F2cWN9fY8rei2qaxweIaRgq+ek8TqfoFIsUjaGNKGGEHElSg==", + "requires": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + } + }, + "cacache": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-14.0.0.tgz", + "integrity": "sha512-+Nr/BnA/tjAUXza9gH8F+FSP+1HvWqCKt4c95dQr4EDVJVafbzmPZpLKCkLYexs6vSd2B/1TOXrAoNnqVPfvRA==", + "requires": { + "chownr": "^1.1.2", + "figgy-pudding": "^3.5.1", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.2", + "infer-owner": "^1.0.4", + "lru-cache": "^5.1.1", + "minipass": "^3.0.0", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "move-concurrently": "^1.0.1", + "p-map": "^3.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^2.7.1", + "ssri": "^7.0.0", + "tar": "^6.0.0", + "unique-filename": "^1.1.1" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" + }, + "http-proxy-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-3.0.0.tgz", + "integrity": "sha512-uGuJaBWQWDQCJI5ip0d/VTYZW0nRrlLWXA4A7P1jrsa+f77rW2yXz315oBt6zGCF6l8C2tlMxY7ffULCj+5FhA==", + "requires": { + "agent-base": "5", + "debug": "4" + } + }, + "make-fetch-happen": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-7.1.1.tgz", + "integrity": "sha512-7fNjiOXNZhNGQzG5P15nU97aZQtzPU2GVgVd7pnqnl5gnpLzMAD8bAe5YG4iW2s0PTqaZy9xGv4Wfqe872kRNQ==", + "requires": { + "agentkeepalive": "^4.1.0", + "cacache": "^14.0.0", + "http-cache-semantics": "^4.0.3", + "http-proxy-agent": "^3.0.0", + "https-proxy-agent": "^4.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^5.1.1", + "minipass": "^3.0.0", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.1.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^4.0.0", + "ssri": "^7.0.1" + } + }, + "minipass": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", + "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", + "requires": { + "yallist": "^4.0.0" + } + }, + "minizlib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz", + "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.3.tgz", + "integrity": "sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + }, + "ssri": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz", + "integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==", + "requires": { + "figgy-pudding": "^3.5.1", + "minipass": "^3.1.1" + } + }, + "tar": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.1.tgz", + "integrity": "sha512-bKhKrrz2FJJj5s7wynxy/fyxpE0CmCjmOQ1KV4KkgXFWOgoIT/NbTMnB1n+LFNrNk0SSBVGGxcK5AGsyC+pW5Q==", + "requires": { + "chownr": "^1.1.3", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.0", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } }, @@ -3949,7 +4126,6 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", - "dev": true, "requires": { "es6-promisify": "^5.0.0" } @@ -3963,6 +4139,22 @@ "humanize-ms": "^1.2.1" } }, + "aggregate-error": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", + "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "dependencies": { + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" + } + } + }, "ajv": { "version": "6.10.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", @@ -4377,8 +4569,7 @@ "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "are-we-there-yet": { "version": "1.1.5", @@ -4717,8 +4908,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base": { "version": "0.11.2", @@ -4873,7 +5063,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5173,8 +5362,7 @@ "chownr": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", - "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==", - "dev": true + "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==" }, "ci-info": { "version": "2.0.0", @@ -5205,6 +5393,11 @@ } } }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" + }, "cli-cursor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", @@ -5417,8 +5610,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "1.6.2", @@ -5874,7 +6066,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "dev": true, "requires": { "aproba": "^1.1.1", "fs-write-stream-atomic": "^1.0.8", @@ -5903,8 +6094,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cors": { "version": "2.8.5", @@ -6379,7 +6569,6 @@ "version": "0.1.12", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", - "dev": true, "requires": { "iconv-lite": "~0.4.13" } @@ -6408,8 +6597,7 @@ "err-code": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz", - "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=", - "dev": true + "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=" }, "error-ex": { "version": "1.3.2", @@ -6451,14 +6639,12 @@ "es6-promise": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.6.tgz", - "integrity": "sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q==", - "dev": true + "integrity": "sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q==" }, "es6-promisify": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "dev": true, "requires": { "es6-promise": "^4.0.3" } @@ -7069,8 +7255,7 @@ "figgy-pudding": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", - "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", - "dev": true + "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==" }, "figures": { "version": "2.0.0", @@ -7320,7 +7505,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "dev": true, "requires": { "graceful-fs": "^4.1.2", "iferr": "^0.1.5", @@ -7331,14 +7515,12 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -7353,7 +7535,6 @@ "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" } @@ -7363,8 +7544,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "1.2.9", @@ -7869,7 +8049,6 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -7927,8 +8106,7 @@ "graceful-fs": { "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" }, "graphql": { "version": "14.6.0", @@ -8483,7 +8661,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", - "dev": true, "requires": { "agent-base": "5", "debug": "4" @@ -8492,14 +8669,12 @@ "agent-base": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", - "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", - "dev": true + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==" }, "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" } @@ -8507,8 +8682,7 @@ "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 + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -8516,7 +8690,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", - "dev": true, "requires": { "ms": "^2.0.0" } @@ -8525,7 +8698,6 @@ "version": "0.4.23", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", - "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -8533,8 +8705,7 @@ "iferr": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", - "dev": true + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" }, "ignore": { "version": "4.0.6", @@ -8574,8 +8745,7 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "indent-string": { "version": "3.2.0", @@ -8586,8 +8756,7 @@ "infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" }, "inflation": { "version": "2.0.0", @@ -8598,7 +8767,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -8735,8 +8903,7 @@ "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", - "dev": true + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" }, "ipaddr.js": { "version": "1.8.0", @@ -8887,6 +9054,11 @@ "is-extglob": "^2.1.1" } }, + "is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=" + }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -12281,7 +12453,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -12312,6 +12483,134 @@ "yallist": "^3.0.0" } }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", + "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "minipass-fetch": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.2.1.tgz", + "integrity": "sha512-ssHt0dkljEDaKmTgQ04DQgx2ag6G2gMPxA5hpcsoeTbfDgRf2fC2gNSRc6kISjD7ckCpHwwQvXxuTBK8402fXg==", + "requires": { + "encoding": "^0.1.12", + "minipass": "^3.1.0", + "minipass-pipeline": "^1.2.2", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", + "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", + "requires": { + "yallist": "^4.0.0" + } + }, + "minizlib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz", + "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", + "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "minipass-pipeline": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.2.tgz", + "integrity": "sha512-3JS5A2DKhD2g0Gg8x3yamO0pj7YeKGwVlDS90pF++kxptwx/F+B//roxf9SqYil5tQo65bijy+dAuAFZmYOouA==", + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", + "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", + "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, "minizlib": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", @@ -12364,7 +12663,6 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, "requires": { "minimist": "0.0.8" }, @@ -12372,8 +12670,7 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" } } }, @@ -12402,7 +12699,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", - "dev": true, "requires": { "aproba": "^1.1.1", "copy-concurrently": "^1.0.0", @@ -12924,7 +13220,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -13212,8 +13507,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-key": { "version": "2.0.1", @@ -13365,20 +13659,17 @@ "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", - "dev": true + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" }, "promise-retry": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz", "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=", - "dev": true, "requires": { "err-code": "^1.0.0", "retry": "^0.10.0" @@ -13387,8 +13678,7 @@ "retry": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", - "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=", - "dev": true + "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=" } } }, @@ -13943,7 +14233,6 @@ "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, "requires": { "glob": "^7.1.3" } @@ -13967,7 +14256,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", - "dev": true, "requires": { "aproba": "^1.1.1" } @@ -14189,8 +14477,7 @@ "smart-buffer": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz", - "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==", - "dev": true + "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==" }, "snapdragon": { "version": "0.8.2", @@ -14309,7 +14596,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz", "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==", - "dev": true, "requires": { "ip": "1.1.5", "smart-buffer": "^4.1.0" @@ -14319,7 +14605,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz", "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==", - "dev": true, "requires": { "agent-base": "~4.2.1", "socks": "~2.3.2" @@ -15214,7 +15499,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, "requires": { "unique-slug": "^2.0.0" } @@ -15223,7 +15507,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "dev": true, "requires": { "imurmurhash": "^0.1.4" } @@ -15335,8 +15618,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "util-promisify": { "version": "2.1.0", @@ -15543,8 +15825,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write-file-atomic": { "version": "2.4.1", diff --git a/packages/apollo-federation/jest.config.js b/packages/apollo-federation/jest.config.js index a0453dd2303..a54e180ea6d 100644 --- a/packages/apollo-federation/jest.config.js +++ b/packages/apollo-federation/jest.config.js @@ -1,8 +1,19 @@ const config = require('../../jest.config.base'); -module.exports = Object.assign(Object.create(null), config, { +const NODE_MAJOR_VERSION = parseInt( + process.versions.node.split('.', 1)[0], + 10 +); + +const additionalConfig = { setupFiles: [ 'core-js/features/array/flat', 'core-js/features/array/flat-map', ], -}); + testPathIgnorePatterns: [ + ...config.testPathIgnorePatterns, + ...NODE_MAJOR_VERSION === 6 ? [""] : [] + ] +}; + +module.exports = Object.assign(Object.create(null), config, additionalConfig); diff --git a/packages/apollo-gateway/CHANGELOG.md b/packages/apollo-gateway/CHANGELOG.md index ebcacd8e9fa..5407a9e8d4a 100644 --- a/packages/apollo-gateway/CHANGELOG.md +++ b/packages/apollo-gateway/CHANGELOG.md @@ -7,6 +7,7 @@ - __BREAKING__: The behavior and signature of `RemoteGraphQLDataSource`'s `didReceiveResponse` method has been changed. No changes are necessary _unless_ your implementation has overridden the default behavior of this method by either extending the class and overriding the method or by providing `didReceiveResponse` as a parameter to the `RemoteGraphQLDataSource`'s constructor options. Implementations which have provided their own `didReceiveResponse` using either of these methods should view the PR linked here for details on what has changed. [PR #3743](https://github.com/apollographql/apollo-server/pull/3743) - __NEW__: Setting the `apq` option to `true` on the `RemoteGraphQLDataSource` will enable the use of [automated persisted queries (APQ)](https://www.apollographql.com/docs/apollo-server/performance/apq/) when sending queries to downstream services. Depending on the complexity of queries sent to downstream services, this technique can greatly reduce the size of the payloads being transmitted over the network. Downstream implementing services must also support APQ functionality to participate in this feature (Apollo Server does by default unless it has been explicitly disabled). As with normal APQ behavior, a downstream server must have received and registered a query once before it will be able to serve an APQ request. [#3744](https://github.com/apollographql/apollo-server/pull/3744) - __NEW__: Experimental feature: compress downstream requests via generated fragments [#3791](https://github.com/apollographql/apollo-server/pull/3791) This feature enables the gateway to generate fragments for queries to downstream services in order to minimize bytes over the wire and parse time. This can be enabled via the gateway config by setting `experimental_autoFragmentization: true`. It is currently disabled by default. +- Introduce `make-fetch-happen` package. Remove `cachedFetcher` in favor of the caching implementation provided by this package. [#3783](https://github.com/apollographql/apollo-server/pull/3783/files) ## v0.12.1 diff --git a/packages/apollo-gateway/jest.config.js b/packages/apollo-gateway/jest.config.js index 7cdfbc1e316..29528a49090 100644 --- a/packages/apollo-gateway/jest.config.js +++ b/packages/apollo-gateway/jest.config.js @@ -1,7 +1,17 @@ const path = require('path'); const config = require('../../jest.config.base'); +const NODE_MAJOR_VERSION = parseInt( + process.versions.node.split('.', 1)[0], + 10 +); + const additionalConfig = { setupFilesAfterEnv: [path.resolve(__dirname, './src/__tests__/testSetup.ts')], + testPathIgnorePatterns: [ + ...config.testPathIgnorePatterns, + ...NODE_MAJOR_VERSION === 6 ? [""] : [] + ] }; -module.exports = Object.assign(Object.create(null), additionalConfig, config); + +module.exports = Object.assign(Object.create(null), config, additionalConfig); diff --git a/packages/apollo-gateway/package.json b/packages/apollo-gateway/package.json index 5cc8c02033a..0e78998a364 100644 --- a/packages/apollo-gateway/package.json +++ b/packages/apollo-gateway/package.json @@ -31,6 +31,7 @@ "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": { diff --git a/packages/apollo-gateway/src/cache.ts b/packages/apollo-gateway/src/cache.ts new file mode 100644 index 00000000000..e667ae43f07 --- /dev/null +++ b/packages/apollo-gateway/src/cache.ts @@ -0,0 +1,55 @@ +import { CacheManager } from 'make-fetch-happen'; +import { Request, Response, Headers } from 'apollo-server-env'; +import { InMemoryLRUCache } from 'apollo-server-caching'; + +const MAX_SIZE = 5 * 1024 * 1024; // 5MB + +function cacheKey(request: Request) { + return `gateway:request-cache:${request.method}:${request.url}`; +} + +interface CachedRequest { + body: string; + status: number; + statusText: string; + headers: Headers; +} + +export class HttpRequestCache implements CacheManager { + constructor( + public cache: InMemoryLRUCache = new InMemoryLRUCache({ + maxSize: MAX_SIZE, + }), + ) {} + + // Return true if entry exists, else false + async delete(request: Request) { + const key = cacheKey(request); + const entry = await this.cache.get(key); + await this.cache.delete(key); + return Boolean(entry); + } + + async put(request: Request, response: Response) { + let body = await response.text(); + + this.cache.set(cacheKey(request), { + body, + status: response.status, + statusText: response.statusText, + headers: response.headers, + }); + + return new Response(body, response); + } + + async match(request: Request) { + return this.cache.get(cacheKey(request)).then(response => { + if (response) { + const { body, ...requestInit } = response; + return new Response(body, requestInit); + } + return; + }); + } +} diff --git a/packages/apollo-gateway/src/cachedFetcher.ts b/packages/apollo-gateway/src/cachedFetcher.ts deleted file mode 100644 index c8a366baf79..00000000000 --- a/packages/apollo-gateway/src/cachedFetcher.ts +++ /dev/null @@ -1,69 +0,0 @@ -import fetch, { RequestInit, Response } from 'node-fetch'; - -export interface CachedFetcherOptions { - pollInterval?: number; -} - -export class CachedFetcher { - private logPrefix = 'CachedFetcher: '; - private mapUrlToLastSuccessfulETag: { [url: string]: string } = Object.create( - null, - ); - private mapUrlToCachedResult: { [url: string]: any } = Object.create(null); - - async fetch(url: string) { - const fetchOptions: RequestInit = { - // GET is what we request, but keep in mind that, when we include and get - // a match on the `If-None-Match` header we'll get an early return with a - // status code 304. - method: 'GET', - headers: Object.create(null), - }; - - const lastSuccessfulETag = this.mapUrlToLastSuccessfulETag[url]; - if (lastSuccessfulETag) { - fetchOptions.headers = { - ...fetchOptions.headers, - 'If-None-Match': lastSuccessfulETag, - }; - } - - let response: Response; - try { - // TODO: Post-EA, implement more retry-ability - response = await fetch(url, fetchOptions); - } catch (error) { - throw error; - } - - /* - Entity tags uniquely representing the requested resources. - They are a string of ASCII characters placed between double quotes - (Like "675af34563dc-tr34") and may be prefixed by W/ to indicate - that the weak comparison algorithm should be used - (This is useless with If-None-Match as it only uses that algorithm). - */ - const receivedETag = response.headers.get('etag'); - // Cache hit, early return - if (response.status === 304) { - return { isCacheHit: true, result: this.mapUrlToCachedResult[url] }; - } - - if (!response.ok) { - throw new Error( - `${this.logPrefix}Could not fetch ${await response.text()}`, - ); - } - - if (receivedETag) { - this.mapUrlToLastSuccessfulETag[url] = receivedETag; - } - - this.mapUrlToCachedResult[url] = await response.text(); - return { isCacheHit: false, result: this.mapUrlToCachedResult[url] }; - } - - getCache() { - return this.mapUrlToCachedResult; - } -} diff --git a/packages/apollo-gateway/src/index.ts b/packages/apollo-gateway/src/index.ts index e5f020c18f5..0248603a0d4 100644 --- a/packages/apollo-gateway/src/index.ts +++ b/packages/apollo-gateway/src/index.ts @@ -40,7 +40,8 @@ import { GraphQLDataSource } from './datasources/types'; import { RemoteGraphQLDataSource } from './datasources/RemoteGraphQLDataSource'; import { HeadersInit } from 'node-fetch'; import { getVariableValues } from 'graphql/execution/values'; -import { CachedFetcher } from './cachedFetcher'; +import fetcher, { Fetcher } from 'make-fetch-happen'; +import { HttpRequestCache } from './cache'; export type ServiceEndpointDefinition = Pick; @@ -60,6 +61,7 @@ interface GatewayConfigBase { experimental_pollInterval?: number; experimental_approximateQueryPlanStoreMiB?: number; experimental_autoFragmentization?: boolean; + fetcher?: Fetcher; } interface RemoteGatewayConfig extends GatewayConfigBase { @@ -161,7 +163,10 @@ export class ApolloGateway implements GraphQLService { private serviceDefinitions: ServiceDefinition[] = []; private compositionMetadata?: CompositionMetadata; private serviceSdlCache = new Map(); - private fetcher = new CachedFetcher(); + + private fetcher: Fetcher = fetcher.defaults({ + cacheManager: new HttpRequestCache() + }); // Observe query plan, service info, and operation info prior to execution. // The information made available here will give insight into the resulting @@ -247,6 +252,10 @@ export class ApolloGateway implements GraphQLService { 'If you are polling running services, use with caution.', ); } + + if (config.fetcher) { + this.fetcher = config.fetcher; + } } } diff --git a/packages/apollo-gateway/src/loadServicesFromStorage.ts b/packages/apollo-gateway/src/loadServicesFromStorage.ts index 783d04c6f37..31c18c5aab7 100644 --- a/packages/apollo-gateway/src/loadServicesFromStorage.ts +++ b/packages/apollo-gateway/src/loadServicesFromStorage.ts @@ -1,4 +1,4 @@ -import { CachedFetcher } from './cachedFetcher'; +import { Fetcher } from 'make-fetch-happen'; import { parse } from 'graphql'; import { Experimental_UpdateServiceDefinitions } from '.'; @@ -61,12 +61,14 @@ export async function getServiceDefinitionsFromStorage({ apiKeyHash: string; graphVariant?: string; federationVersion: number; - fetcher: CachedFetcher; + fetcher: Fetcher; }): ReturnType { // fetch the storage secret const storageSecretUrl = getStorageSecretUrl(graphId, apiKeyHash); - const response = await fetcher.fetch(storageSecretUrl); - const secret = JSON.parse(response.result); + + const secret: string = await fetcher(storageSecretUrl).then(response => + response.json(), + ); if (!graphVariant) { graphVariant = 'current'; @@ -74,41 +76,31 @@ export async function getServiceDefinitionsFromStorage({ const baseUrl = `${urlPartialSchemaBase}/${secret}/${graphVariant}/v${federationVersion}`; - const { - isCacheHit: linkFileCacheHit, - result: linkFileResult, - } = await fetcher.fetch(`${baseUrl}/composition-config-link`); - - // If the link file is a cache hit, no further work is needed - if (linkFileCacheHit) return { isNewSchema: false }; + const response = await fetcher(`${baseUrl}/composition-config-link`); - const parsedLink = JSON.parse(linkFileResult) as LinkFileResult; + if (response.status === 304) { + return { isNewSchema: false }; + } - const { result: configFileResult } = await fetcher.fetch( - `${urlPartialSchemaBase}/${parsedLink.configPath}`, - ); + const linkFileResult: LinkFileResult = await response.json(); - const compositionMetadata = JSON.parse( - configFileResult, - ) as CompositionMetadata; + const compositionMetadata: CompositionMetadata = await fetcher( + `${urlPartialSchemaBase}/${linkFileResult.configPath}`, + ).then(response => response.json()); // It's important to maintain the original order here const serviceDefinitions = await Promise.all( compositionMetadata.implementingServiceLocations.map( async ({ name, path }) => { - const serviceLocation = await fetcher.fetch( + const { url, partialSchemaPath }: ImplementingService = await fetcher( `${urlPartialSchemaBase}/${path}`, - ); - - const { url, partialSchemaPath } = JSON.parse( - serviceLocation.result, - ) as ImplementingService; + ).then(response => response.json()); - const { result } = await fetcher.fetch( + const sdl = await fetcher( `${urlPartialSchemaBase}/${partialSchemaPath}`, - ); + ).then(response => response.text()); - return { name, url, typeDefs: parse(result) }; + return { name, url, typeDefs: parse(sdl) }; }, ), ); diff --git a/packages/apollo-gateway/src/make-fetch-happen.d.ts b/packages/apollo-gateway/src/make-fetch-happen.d.ts new file mode 100644 index 00000000000..d83c1945203 --- /dev/null +++ b/packages/apollo-gateway/src/make-fetch-happen.d.ts @@ -0,0 +1,53 @@ +declare module 'make-fetch-happen' { + import { + Response, + Request, + RequestInfo, + RequestInit, + } from 'apollo-server-env'; + + // If adding to these options, they should mirror those from `make-fetch-happen` + // @see: https://github.com/npm/make-fetch-happen/#extra-options + export interface FetcherOptions { + cacheManager?: string | CacheManager; + // @see: https://www.npmjs.com/package/retry#retrytimeoutsoptions + retry?: + | boolean + | number + | { + // The maximum amount of times to retry the operation. Default is 10. Seting this to 1 means do it once, then retry it once + retries?: number; + // The exponential factor to use. Default is 2. + factor?: number; + // The number of milliseconds before starting the first retry. Default is 1000. + minTimeout?: number; + // The maximum number of milliseconds between two retries. Default is Infinity. + maxTimeout?: number; + // Randomizes the timeouts by multiplying with a factor between 1 to 2. Default is false. + randomize?: boolean; + }; + onRetry?(): void; + } + + export interface CacheManager { + delete(req: Request): Promise; + put(req: Request, res: Response): Promise; + match(req: Request): Promise; + } + + /** + * This is an augmentation of the fetch function types provided by `apollo-server-env` + * @see: https://git.io/JvBwX + */ + export interface Fetcher { + (input?: RequestInfo, init?: RequestInit & FetcherOptions): Promise< + Response + >; + } + + let fetch: Fetcher & { + defaults(opts?: FetcherOptions): Fetcher; + }; + + export default fetch; +} From 397a735c807ff5d0caf71910db9448f3d08cc219 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Thu, 20 Feb 2020 20:11:20 +0200 Subject: [PATCH 16/27] Use the logger rather than console where the logger is already available. This doesn't comprehensively fix places where we use `console` but `logger` isn't already in scope, but these are easy wins. cc @trevor-scheer --- packages/apollo-gateway/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/apollo-gateway/src/index.ts b/packages/apollo-gateway/src/index.ts index 0248603a0d4..e61d6e282b6 100644 --- a/packages/apollo-gateway/src/index.ts +++ b/packages/apollo-gateway/src/index.ts @@ -246,7 +246,7 @@ export class ApolloGateway implements GraphQLService { // Warn against using the pollInterval and a serviceList simulatenously if (config.experimental_pollInterval && isRemoteConfig(config)) { - console.warn( + this.logger.warn( 'Polling running services is dangerous and not recommended in production. ' + 'Polling should only be used against a registry. ' + 'If you are polling running services, use with caution.', @@ -285,7 +285,7 @@ export class ApolloGateway implements GraphQLService { // instead of here. We can remove this as a breaking change in the future. if (options && options.engine) { if (!options.engine.graphVariant) - console.warn('No graph variant provided. Defaulting to `current`.'); + this.logger.warn('No graph variant provided. Defaulting to `current`.'); this.engineConfig = options.engine; } From 1d6b89546743346a22bae0643392491d5fdefc9d Mon Sep 17 00:00:00 2001 From: Trevor Scheer Date: Thu, 20 Feb 2020 11:23:28 -0800 Subject: [PATCH 17/27] Gateway: provide `user-agent` header to fetcher (#3810) --- .../src/__tests__/integration/nockMocks.ts | 20 ++++++++++++++----- packages/apollo-gateway/src/index.ts | 8 +++++++- .../apollo-gateway/src/make-fetch-happen.d.ts | 2 +- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/packages/apollo-gateway/src/__tests__/integration/nockMocks.ts b/packages/apollo-gateway/src/__tests__/integration/nockMocks.ts index d0595353c45..b3c516e7272 100644 --- a/packages/apollo-gateway/src/__tests__/integration/nockMocks.ts +++ b/packages/apollo-gateway/src/__tests__/integration/nockMocks.ts @@ -1,5 +1,15 @@ import nock from 'nock'; +function gcsNock(url: Parameters[0]): nock.Scope { + return nock(url, { + reqheaders: { + 'user-agent': `apollo-gateway/${ + require('../../../package.json').version + }`, + }, + }); +} + export const mockLocalhostSDLQuery = ({ url }: { url: string }) => nock(url).post('/graphql', { query: 'query GetServiceDefinition { _service { sdl } }', @@ -12,13 +22,13 @@ export const mockFetchStorageSecret = ({ apiKeyHash: string; serviceName: string; }) => - nock('https://storage.googleapis.com:443').get( + gcsNock('https://storage.googleapis.com:443').get( `/engine-partial-schema-prod/${serviceName}/storage-secret/${apiKeyHash}.json`, ); // get composition config link, using received storage secret export const mockGetCompositionConfigLink = (storageSecret: string) => - nock('https://storage.googleapis.com:443').get( + gcsNock('https://storage.googleapis.com:443').get( `/engine-partial-schema-prod/${storageSecret}/current/v1/composition-config-link`, ); @@ -28,7 +38,7 @@ export const mockGetCompositionConfigs = ({ }: { storageSecret: string; }) => - nock('https://storage.googleapis.com:443').get( + gcsNock('https://storage.googleapis.com:443').get( `/engine-partial-schema-prod/${storageSecret}/current/v1/composition-configs/composition-config-path.json`, ); @@ -42,7 +52,7 @@ export const mockGetImplementingServices = ({ implementingServicePath: string; federatedServiceName: string; }) => - nock('https://storage.googleapis.com:443').get( + gcsNock('https://storage.googleapis.com:443').get( `/engine-partial-schema-prod/${storageSecret}/current/v1/implementing-services/${federatedServiceName}/${implementingServicePath}`, ); @@ -54,6 +64,6 @@ export const mockGetRawPartialSchema = ({ storageSecret: string; partialSchemaPath: string; }) => - nock('https://storage.googleapis.com:443').get( + gcsNock('https://storage.googleapis.com:443').get( `/engine-partial-schema-prod/${storageSecret}/current/raw-partial-schemas/${partialSchemaPath}`, ); diff --git a/packages/apollo-gateway/src/index.ts b/packages/apollo-gateway/src/index.ts index e61d6e282b6..fa34a7ecdde 100644 --- a/packages/apollo-gateway/src/index.ts +++ b/packages/apollo-gateway/src/index.ts @@ -165,7 +165,13 @@ export class ApolloGateway implements GraphQLService { private serviceSdlCache = new Map(); private fetcher: Fetcher = fetcher.defaults({ - cacheManager: new HttpRequestCache() + 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. diff --git a/packages/apollo-gateway/src/make-fetch-happen.d.ts b/packages/apollo-gateway/src/make-fetch-happen.d.ts index d83c1945203..88ed260e033 100644 --- a/packages/apollo-gateway/src/make-fetch-happen.d.ts +++ b/packages/apollo-gateway/src/make-fetch-happen.d.ts @@ -46,7 +46,7 @@ declare module 'make-fetch-happen' { } let fetch: Fetcher & { - defaults(opts?: FetcherOptions): Fetcher; + defaults(opts?: RequestInit & FetcherOptions): Fetcher; }; export default fetch; From c33d01f37af164a11f92a3dece1af261534d2d95 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Mon, 24 Feb 2020 16:44:08 +0200 Subject: [PATCH 18/27] Add `^15.0.0-rc.2` to `peerDependencies` range for `graphql`. (#3825) --- packages/apollo-cache-control/package.json | 2 +- packages/apollo-engine-reporting/package.json | 2 +- packages/apollo-federation/package.json | 2 +- packages/apollo-gateway/package.json | 2 +- packages/apollo-server-azure-functions/package.json | 2 +- packages/apollo-server-cloud-functions/package.json | 2 +- packages/apollo-server-cloudflare/package.json | 2 +- packages/apollo-server-core/package.json | 2 +- packages/apollo-server-errors/package.json | 2 +- packages/apollo-server-express/package.json | 2 +- packages/apollo-server-fastify/package.json | 2 +- packages/apollo-server-hapi/package.json | 2 +- packages/apollo-server-koa/package.json | 2 +- packages/apollo-server-lambda/package.json | 2 +- packages/apollo-server-plugin-base/package.json | 2 +- packages/apollo-server-plugin-response-cache/package.json | 2 +- packages/apollo-server-testing/package.json | 2 +- packages/apollo-server-types/package.json | 2 +- packages/apollo-server/package.json | 2 +- packages/apollo-tracing/package.json | 2 +- packages/graphql-extensions/package.json | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/apollo-cache-control/package.json b/packages/apollo-cache-control/package.json index 7781d9b4266..190e667c1ea 100644 --- a/packages/apollo-cache-control/package.json +++ b/packages/apollo-cache-control/package.json @@ -15,6 +15,6 @@ "graphql-extensions": "file:../graphql-extensions" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-engine-reporting/package.json b/packages/apollo-engine-reporting/package.json index 3bc459034aa..bea70f5980d 100644 --- a/packages/apollo-engine-reporting/package.json +++ b/packages/apollo-engine-reporting/package.json @@ -21,6 +21,6 @@ "graphql-extensions": "file:../graphql-extensions" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-federation/package.json b/packages/apollo-federation/package.json index 4ea06847d33..8aa82c828e7 100644 --- a/packages/apollo-federation/package.json +++ b/packages/apollo-federation/package.json @@ -20,6 +20,6 @@ "lodash.xorby": "^4.7.0" }, "peerDependencies": { - "graphql": "^14.0.2" + "graphql": "^14.0.2 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-gateway/package.json b/packages/apollo-gateway/package.json index 0e78998a364..cbc58ac750c 100644 --- a/packages/apollo-gateway/package.json +++ b/packages/apollo-gateway/package.json @@ -35,6 +35,6 @@ "pretty-format": "^24.7.0" }, "peerDependencies": { - "graphql": "^14.2.1" + "graphql": "^14.2.1 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-server-azure-functions/package.json b/packages/apollo-server-azure-functions/package.json index 0f6a06989c6..a508f77320c 100644 --- a/packages/apollo-server-azure-functions/package.json +++ b/packages/apollo-server-azure-functions/package.json @@ -36,6 +36,6 @@ "apollo-server-integration-testsuite": "file:../apollo-server-integration-testsuite" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-server-cloud-functions/package.json b/packages/apollo-server-cloud-functions/package.json index 113d17b687f..80f4e1ee43b 100644 --- a/packages/apollo-server-cloud-functions/package.json +++ b/packages/apollo-server-cloud-functions/package.json @@ -35,6 +35,6 @@ "apollo-server-integration-testsuite": "file:../apollo-server-integration-testsuite" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.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 7e49c9e3237..89486e7055e 100644 --- a/packages/apollo-server-cloudflare/package.json +++ b/packages/apollo-server-cloudflare/package.json @@ -26,6 +26,6 @@ "apollo-server-types": "file:../apollo-server-types" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.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 877a8890afa..b6b73e2501a 100644 --- a/packages/apollo-server-core/package.json +++ b/packages/apollo-server-core/package.json @@ -47,6 +47,6 @@ "ws": "^6.0.0" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-server-errors/package.json b/packages/apollo-server-errors/package.json index d818b7aa07f..41bc49cf142 100644 --- a/packages/apollo-server-errors/package.json +++ b/packages/apollo-server-errors/package.json @@ -17,6 +17,6 @@ "node": ">=6" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.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 fb8aa7117f1..cfeac2fe8b5 100644 --- a/packages/apollo-server-express/package.json +++ b/packages/apollo-server-express/package.json @@ -47,6 +47,6 @@ "apollo-server-integration-testsuite": "file:../apollo-server-integration-testsuite" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.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 52ea4b0cd45..1c2bd3ab291 100644 --- a/packages/apollo-server-fastify/package.json +++ b/packages/apollo-server-fastify/package.json @@ -37,6 +37,6 @@ "apollo-server-integration-testsuite": "file:../apollo-server-integration-testsuite" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.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 52950ba8de6..f7a45233dc9 100644 --- a/packages/apollo-server-hapi/package.json +++ b/packages/apollo-server-hapi/package.json @@ -37,6 +37,6 @@ "apollo-server-integration-testsuite": "file:../apollo-server-integration-testsuite" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-server-koa/package.json b/packages/apollo-server-koa/package.json index da432b26b43..2ae1b767539 100644 --- a/packages/apollo-server-koa/package.json +++ b/packages/apollo-server-koa/package.json @@ -48,6 +48,6 @@ "apollo-server-integration-testsuite": "file:../apollo-server-integration-testsuite" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.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 f967061bedc..8345b99d031 100644 --- a/packages/apollo-server-lambda/package.json +++ b/packages/apollo-server-lambda/package.json @@ -36,6 +36,6 @@ "apollo-server-integration-testsuite": "file:../apollo-server-integration-testsuite" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.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/package.json b/packages/apollo-server-plugin-base/package.json index 7bfeed3917f..4ffa135aac0 100644 --- a/packages/apollo-server-plugin-base/package.json +++ b/packages/apollo-server-plugin-base/package.json @@ -14,6 +14,6 @@ "apollo-server-types": "file:../apollo-server-types" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.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/package.json b/packages/apollo-server-plugin-response-cache/package.json index cb6d5f9db77..dee6f475b85 100644 --- a/packages/apollo-server-plugin-response-cache/package.json +++ b/packages/apollo-server-plugin-response-cache/package.json @@ -27,6 +27,6 @@ "apollo-server-types": "file:../apollo-server-types" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-server-testing/package.json b/packages/apollo-server-testing/package.json index 4e3216b6f96..422c63b3e4b 100644 --- a/packages/apollo-server-testing/package.json +++ b/packages/apollo-server-testing/package.json @@ -30,6 +30,6 @@ "apollo-server-types": "file:../apollo-server-types" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.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 0f973507a19..2edc690d526 100644 --- a/packages/apollo-server-types/package.json +++ b/packages/apollo-server-types/package.json @@ -16,6 +16,6 @@ "apollo-server-env": "file:../apollo-server-env" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-server/package.json b/packages/apollo-server/package.json index 9fed1126f97..1bb3e2d3719 100644 --- a/packages/apollo-server/package.json +++ b/packages/apollo-server/package.json @@ -28,6 +28,6 @@ "graphql-tools": "^4.0.0" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } diff --git a/packages/apollo-tracing/package.json b/packages/apollo-tracing/package.json index f3e1fd4b3bb..173ed76ecbc 100644 --- a/packages/apollo-tracing/package.json +++ b/packages/apollo-tracing/package.json @@ -15,6 +15,6 @@ "graphql-extensions": "file:../graphql-extensions" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.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 b729295be6a..3f9b3746b67 100644 --- a/packages/graphql-extensions/package.json +++ b/packages/graphql-extensions/package.json @@ -19,6 +19,6 @@ "apollo-server-types": "file:../apollo-server-types" }, "peerDependencies": { - "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc.2" } } From f5abe1e5d7c2cf56fe2b0d66c89c0014875ef72d Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Mon, 24 Feb 2020 17:03:34 +0200 Subject: [PATCH 19/27] Add CHANGELOG.md for #3825. Ref: https://github.com/apollographql/apollo-server/pull/3825 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26e8a30ce28..02b187d6177 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ The version headers in this history reflect the versions of Apollo Server itself ### v2.11.0 -- _Nothing yet! Stay tuned._ +- The range of accepted `peerDepedencies` versions for `graphql` has been widened to include `graphql@^15.0.0-rc.2` so as to accommodate the latest release-candidate of the `graphql@15` package, and an intention to support it when it is finally released on the `latest` npm tag. While this change will subdue peer dependency warnings for Apollo Server packages, many dependencies from outside of this repository will continue to raise similar warnings until those packages own `peerDependencies` are updated. It is unlikely that all of those packages will update their ranges prior to the final version of `graphql@15` being released, but if everything is working as expected, the warnings can be safely ignored. [PR #3825](https://github.com/apollographql/apollo-server/pull/3825) ### v2.10.1 From 4f911ba97d8a7cfd4a3bbe54b795bf222f8a1251 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Mon, 24 Feb 2020 17:03:52 +0200 Subject: [PATCH 20/27] Update CHANGELOG.md prior to prerelease. --- packages/apollo-federation/CHANGELOG.md | 2 +- packages/apollo-gateway/CHANGELOG.md | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/apollo-federation/CHANGELOG.md b/packages/apollo-federation/CHANGELOG.md index 20e6d92ff16..a4f2d9e93dc 100644 --- a/packages/apollo-federation/CHANGELOG.md +++ b/packages/apollo-federation/CHANGELOG.md @@ -1,6 +1,6 @@ # CHANGELOG for `@apollo/federation` -## 0.13.1 (pre-release; `@next` tag) +## 0.13.2 (pre-release; `@next` tag) - Only changes in the similarly versioned `@apollo/gateway` package. diff --git a/packages/apollo-gateway/CHANGELOG.md b/packages/apollo-gateway/CHANGELOG.md index 5407a9e8d4a..7cfaed85024 100644 --- a/packages/apollo-gateway/CHANGELOG.md +++ b/packages/apollo-gateway/CHANGELOG.md @@ -1,8 +1,6 @@ # CHANGELOG for `@apollo/gateway` -## 0.13.1 (pre-release; `@next` tag) - -> The changes noted within this `vNEXT` section have not been released yet. New PRs and commits which introduce changes should include an entry in this `vNEXT` section as part of their development. When a release is being prepared, a new header will be (manually) created below and the the appropriate changes within that release will be moved into the new section. +## 0.13.2 (pre-release; `@next` tag) - __BREAKING__: The behavior and signature of `RemoteGraphQLDataSource`'s `didReceiveResponse` method has been changed. No changes are necessary _unless_ your implementation has overridden the default behavior of this method by either extending the class and overriding the method or by providing `didReceiveResponse` as a parameter to the `RemoteGraphQLDataSource`'s constructor options. Implementations which have provided their own `didReceiveResponse` using either of these methods should view the PR linked here for details on what has changed. [PR #3743](https://github.com/apollographql/apollo-server/pull/3743) - __NEW__: Setting the `apq` option to `true` on the `RemoteGraphQLDataSource` will enable the use of [automated persisted queries (APQ)](https://www.apollographql.com/docs/apollo-server/performance/apq/) when sending queries to downstream services. Depending on the complexity of queries sent to downstream services, this technique can greatly reduce the size of the payloads being transmitted over the network. Downstream implementing services must also support APQ functionality to participate in this feature (Apollo Server does by default unless it has been explicitly disabled). As with normal APQ behavior, a downstream server must have received and registered a query once before it will be able to serve an APQ request. [#3744](https://github.com/apollographql/apollo-server/pull/3744) From 71e5956e2233928723b478604a72f2065f654090 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Mon, 24 Feb 2020 17:04:24 +0200 Subject: [PATCH 21/27] Update `version` in `@apollo/federation`'s `package.json` to reflect reality. --- packages/apollo-federation/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/apollo-federation/package.json b/packages/apollo-federation/package.json index 8aa82c828e7..34118f1c1f5 100644 --- a/packages/apollo-federation/package.json +++ b/packages/apollo-federation/package.json @@ -1,6 +1,6 @@ { "name": "@apollo/federation", - "version": "0.13.1-alpha.0", + "version": "0.13.2-alpha.0", "description": "Apollo Federation Utilities", "main": "dist/index.js", "types": "dist/index.d.ts", From a07ce0fba5a78d6e1a3c0a08dfda1b449e6c16e0 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Mon, 24 Feb 2020 17:16:48 +0200 Subject: [PATCH 22/27] Release - apollo-cache-control@0.9.0-alpha.1 - apollo-datasource-rest@0.8.0-alpha.1 - apollo-engine-reporting@1.7.0-alpha.1 - @apollo/federation@0.13.2-alpha.1 - @apollo/gateway@0.13.2-alpha.1 - apollo-server-azure-functions@2.11.0-alpha.1 - apollo-server-cloud-functions@2.11.0-alpha.1 - apollo-server-cloudflare@2.11.0-alpha.1 - apollo-server-core@2.11.0-alpha.1 - apollo-server-errors@2.4.0-alpha.1 - apollo-server-express@2.11.0-alpha.1 - apollo-server-fastify@2.11.0-alpha.1 - apollo-server-hapi@2.11.0-alpha.1 - apollo-server-integration-testsuite@2.11.0-alpha.1 - apollo-server-koa@2.11.0-alpha.1 - apollo-server-lambda@2.11.0-alpha.1 - apollo-server-micro@2.11.0-alpha.1 - apollo-server-plugin-base@0.7.0-alpha.1 - apollo-server-plugin-response-cache@0.4.0-alpha.1 - apollo-server-testing@2.11.0-alpha.1 - apollo-server-types@0.3.0-alpha.1 - apollo-server@2.11.0-alpha.1 - apollo-tracing@0.9.0-alpha.1 - graphql-extensions@0.11.0-alpha.1 --- packages/apollo-cache-control/package.json | 2 +- packages/apollo-datasource-rest/package.json | 2 +- packages/apollo-engine-reporting/package.json | 2 +- packages/apollo-federation/package.json | 2 +- packages/apollo-gateway/package.json | 2 +- packages/apollo-server-azure-functions/package.json | 2 +- packages/apollo-server-cloud-functions/package.json | 2 +- packages/apollo-server-cloudflare/package.json | 2 +- packages/apollo-server-core/package.json | 2 +- packages/apollo-server-errors/package.json | 2 +- packages/apollo-server-express/package.json | 2 +- packages/apollo-server-fastify/package.json | 2 +- packages/apollo-server-hapi/package.json | 2 +- packages/apollo-server-integration-testsuite/package.json | 2 +- packages/apollo-server-koa/package.json | 2 +- packages/apollo-server-lambda/package.json | 2 +- packages/apollo-server-micro/package.json | 2 +- packages/apollo-server-plugin-base/package.json | 2 +- packages/apollo-server-plugin-response-cache/package.json | 2 +- packages/apollo-server-testing/package.json | 2 +- packages/apollo-server-types/package.json | 2 +- packages/apollo-server/package.json | 2 +- packages/apollo-tracing/package.json | 2 +- packages/graphql-extensions/package.json | 2 +- 24 files changed, 24 insertions(+), 24 deletions(-) diff --git a/packages/apollo-cache-control/package.json b/packages/apollo-cache-control/package.json index 190e667c1ea..d2eb5dd5741 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.8.11", + "version": "0.9.0-alpha.1", "description": "A GraphQL extension for cache control", "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/packages/apollo-datasource-rest/package.json b/packages/apollo-datasource-rest/package.json index 00c98c29a40..def2d634224 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.7.0", + "version": "0.8.0-alpha.1", "author": "opensource@apollographql.com", "license": "MIT", "repository": { diff --git a/packages/apollo-engine-reporting/package.json b/packages/apollo-engine-reporting/package.json index bea70f5980d..61284c7d4a2 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.6.0", + "version": "1.7.0-alpha.1", "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", diff --git a/packages/apollo-federation/package.json b/packages/apollo-federation/package.json index 34118f1c1f5..1407e3b75ac 100644 --- a/packages/apollo-federation/package.json +++ b/packages/apollo-federation/package.json @@ -1,6 +1,6 @@ { "name": "@apollo/federation", - "version": "0.13.2-alpha.0", + "version": "0.13.2-alpha.1", "description": "Apollo Federation Utilities", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-gateway/package.json b/packages/apollo-gateway/package.json index cbc58ac750c..73535321daa 100644 --- a/packages/apollo-gateway/package.json +++ b/packages/apollo-gateway/package.json @@ -1,6 +1,6 @@ { "name": "@apollo/gateway", - "version": "0.13.2-alpha.0", + "version": "0.13.2-alpha.1", "description": "Apollo Gateway", "author": "opensource@apollographql.com", "main": "dist/index.js", diff --git a/packages/apollo-server-azure-functions/package.json b/packages/apollo-server-azure-functions/package.json index a508f77320c..41e2417d533 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.10.1", + "version": "2.11.0-alpha.1", "description": "Production-ready Node.js GraphQL server for Azure Functions", "keywords": [ "GraphQL", diff --git a/packages/apollo-server-cloud-functions/package.json b/packages/apollo-server-cloud-functions/package.json index 80f4e1ee43b..b726425a4a0 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.10.1", + "version": "2.11.0-alpha.1", "description": "Production-ready Node.js GraphQL server for Google Cloud Functions", "keywords": [ "GraphQL", diff --git a/packages/apollo-server-cloudflare/package.json b/packages/apollo-server-cloudflare/package.json index 89486e7055e..30918c97fa1 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.10.1", + "version": "2.11.0-alpha.1", "description": "Production-ready Node.js GraphQL server for Cloudflare workers", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-core/package.json b/packages/apollo-server-core/package.json index b6b73e2501a..2f11f68731a 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.10.1", + "version": "2.11.0-alpha.1", "description": "Core engine for Apollo GraphQL server", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-errors/package.json b/packages/apollo-server-errors/package.json index 41bc49cf142..01fdf52f0f4 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.3.4", + "version": "2.4.0-alpha.1", "author": "opensource@apollographql.com", "license": "MIT", "repository": { diff --git a/packages/apollo-server-express/package.json b/packages/apollo-server-express/package.json index cfeac2fe8b5..c3311530e1c 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.10.1", + "version": "2.11.0-alpha.1", "description": "Production-ready Node.js GraphQL server for Express and Connect", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-fastify/package.json b/packages/apollo-server-fastify/package.json index 1c2bd3ab291..6b8a14579b3 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.10.1", + "version": "2.11.0-alpha.1", "description": "Production-ready Node.js GraphQL server for Fastify", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-hapi/package.json b/packages/apollo-server-hapi/package.json index f7a45233dc9..4967e78c328 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.10.1", + "version": "2.11.0-alpha.1", "description": "Production-ready Node.js GraphQL server for Hapi", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-integration-testsuite/package.json b/packages/apollo-server-integration-testsuite/package.json index 30a3b9dcb3b..2275cee755d 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.10.1", + "version": "2.11.0-alpha.1", "description": "Apollo Server Integrations testsuite", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-koa/package.json b/packages/apollo-server-koa/package.json index 2ae1b767539..7703464f1aa 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.10.1", + "version": "2.11.0-alpha.1", "description": "Production-ready Node.js GraphQL server for Koa", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-lambda/package.json b/packages/apollo-server-lambda/package.json index 8345b99d031..cd409f4549f 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.10.1", + "version": "2.11.0-alpha.1", "description": "Production-ready Node.js GraphQL server for AWS Lambda", "keywords": [ "GraphQL", diff --git a/packages/apollo-server-micro/package.json b/packages/apollo-server-micro/package.json index 6cbd9bf24ec..409e332749c 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.10.1", + "version": "2.11.0-alpha.1", "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 4ffa135aac0..50c33e310d1 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.6.10", + "version": "0.7.0-alpha.1", "description": "Apollo Server plugin base classes", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-plugin-response-cache/package.json b/packages/apollo-server-plugin-response-cache/package.json index dee6f475b85..4b2eadfde3e 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.3.11", + "version": "0.4.0-alpha.1", "description": "Apollo Server full query response cache", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-testing/package.json b/packages/apollo-server-testing/package.json index 422c63b3e4b..ddfd3808451 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.10.1", + "version": "2.11.0-alpha.1", "description": "Test utils for apollo-server", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-types/package.json b/packages/apollo-server-types/package.json index 2edc690d526..6512808a503 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.2.10", + "version": "0.3.0-alpha.1", "description": "Apollo Server shared types", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server/package.json b/packages/apollo-server/package.json index 1bb3e2d3719..11fa129050d 100644 --- a/packages/apollo-server/package.json +++ b/packages/apollo-server/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server", - "version": "2.10.1", + "version": "2.11.0-alpha.1", "description": "Production ready GraphQL Server", "author": "opensource@apollographql.com", "main": "dist/index.js", diff --git a/packages/apollo-tracing/package.json b/packages/apollo-tracing/package.json index 173ed76ecbc..f14cc47f3bb 100644 --- a/packages/apollo-tracing/package.json +++ b/packages/apollo-tracing/package.json @@ -1,6 +1,6 @@ { "name": "apollo-tracing", - "version": "0.8.11", + "version": "0.9.0-alpha.1", "description": "Collect and expose trace data for GraphQL requests", "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/packages/graphql-extensions/package.json b/packages/graphql-extensions/package.json index 3f9b3746b67..94074ae7c78 100644 --- a/packages/graphql-extensions/package.json +++ b/packages/graphql-extensions/package.json @@ -1,6 +1,6 @@ { "name": "graphql-extensions", - "version": "0.10.10", + "version": "0.11.0-alpha.1", "description": "Add extensions to GraphQL servers", "main": "./dist/index.js", "types": "./dist/index.d.ts", From daac9ee06f159f111fc9fc375d965bbdec0db125 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Tue, 25 Feb 2020 17:28:44 +0200 Subject: [PATCH 23/27] =?UTF-8?q?Switch=20to=20existing=20`fetch`=20type,?= =?UTF-8?q?=20rather=20than=20that=20of=20`make-fetc=E2=80=A6=20(#3829)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These types are compatible, but this change is necessary to account for the fact that, while we have provided types for `make-fetch-happen` which matter during development of Apollo Server, we don't include those types in the distribution. We could do that, of course, but as I'll note in a minute, we want something more generic anyhow. The omission of the types in the published package (currently, before this commit) results in typing errors for consumers of `@apollo/gateway` since the `import` statement for the `Fetcher` type from `make-fetch-happen` is still emitted (see attached [[Screenshot]]). Switching to the type that `apollo-serve-env` already provides via its `fetch` implementation (literally, `typeof fetch`) should be a no-op change and actually provide the more generic compatibility we want for those who want to bring-their-own-fetch. This should be the same as any type provided by other Fetch-compatible packages as well, including `isomorphic-fetch`, etc. [Screenshot]: https://cc.jro.cc/E0uqEXrX --- packages/apollo-gateway/src/index.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/apollo-gateway/src/index.ts b/packages/apollo-gateway/src/index.ts index fa34a7ecdde..a34db0a362d 100644 --- a/packages/apollo-gateway/src/index.ts +++ b/packages/apollo-gateway/src/index.ts @@ -40,8 +40,9 @@ import { GraphQLDataSource } from './datasources/types'; import { RemoteGraphQLDataSource } from './datasources/RemoteGraphQLDataSource'; import { HeadersInit } from 'node-fetch'; import { getVariableValues } from 'graphql/execution/values'; -import fetcher, { Fetcher } from 'make-fetch-happen'; +import fetcher from 'make-fetch-happen'; import { HttpRequestCache } from './cache'; +import { fetch } from 'apollo-server-env'; export type ServiceEndpointDefinition = Pick; @@ -61,7 +62,7 @@ interface GatewayConfigBase { experimental_pollInterval?: number; experimental_approximateQueryPlanStoreMiB?: number; experimental_autoFragmentization?: boolean; - fetcher?: Fetcher; + fetcher?: typeof fetch; } interface RemoteGatewayConfig extends GatewayConfigBase { @@ -164,7 +165,7 @@ export class ApolloGateway implements GraphQLService { private compositionMetadata?: CompositionMetadata; private serviceSdlCache = new Map(); - private fetcher: Fetcher = fetcher.defaults({ + 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). From 5235abdaeebcf2000b49172474df150d0dd4a247 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Tue, 25 Feb 2020 17:43:52 +0200 Subject: [PATCH 24/27] Release - @apollo/federation@0.13.2-alpha.2 - @apollo/gateway@0.13.2-alpha.2 - apollo-server-azure-functions@2.11.0-alpha.2 - apollo-server-cloud-functions@2.11.0-alpha.2 - apollo-server-cloudflare@2.11.0-alpha.2 - apollo-server-core@2.11.0-alpha.2 - apollo-server-express@2.11.0-alpha.2 - apollo-server-fastify@2.11.0-alpha.2 - apollo-server-hapi@2.11.0-alpha.2 - apollo-server-integration-testsuite@2.11.0-alpha.2 - apollo-server-koa@2.11.0-alpha.2 - apollo-server-lambda@2.11.0-alpha.2 - apollo-server-micro@2.11.0-alpha.2 - apollo-server-testing@2.11.0-alpha.2 - apollo-server@2.11.0-alpha.2 --- packages/apollo-federation/package.json | 2 +- packages/apollo-gateway/package.json | 2 +- packages/apollo-server-azure-functions/package.json | 2 +- packages/apollo-server-cloud-functions/package.json | 2 +- packages/apollo-server-cloudflare/package.json | 2 +- packages/apollo-server-core/package.json | 2 +- packages/apollo-server-express/package.json | 2 +- packages/apollo-server-fastify/package.json | 2 +- packages/apollo-server-hapi/package.json | 2 +- packages/apollo-server-integration-testsuite/package.json | 2 +- packages/apollo-server-koa/package.json | 2 +- packages/apollo-server-lambda/package.json | 2 +- packages/apollo-server-micro/package.json | 2 +- packages/apollo-server-testing/package.json | 2 +- packages/apollo-server/package.json | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/apollo-federation/package.json b/packages/apollo-federation/package.json index 1407e3b75ac..dcaed4fc996 100644 --- a/packages/apollo-federation/package.json +++ b/packages/apollo-federation/package.json @@ -1,6 +1,6 @@ { "name": "@apollo/federation", - "version": "0.13.2-alpha.1", + "version": "0.13.2-alpha.2", "description": "Apollo Federation Utilities", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-gateway/package.json b/packages/apollo-gateway/package.json index 73535321daa..d9311008c0e 100644 --- a/packages/apollo-gateway/package.json +++ b/packages/apollo-gateway/package.json @@ -1,6 +1,6 @@ { "name": "@apollo/gateway", - "version": "0.13.2-alpha.1", + "version": "0.13.2-alpha.2", "description": "Apollo Gateway", "author": "opensource@apollographql.com", "main": "dist/index.js", diff --git a/packages/apollo-server-azure-functions/package.json b/packages/apollo-server-azure-functions/package.json index 41e2417d533..a9935e885f1 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.11.0-alpha.1", + "version": "2.11.0-alpha.2", "description": "Production-ready Node.js GraphQL server for Azure Functions", "keywords": [ "GraphQL", diff --git a/packages/apollo-server-cloud-functions/package.json b/packages/apollo-server-cloud-functions/package.json index b726425a4a0..54ff913e994 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.11.0-alpha.1", + "version": "2.11.0-alpha.2", "description": "Production-ready Node.js GraphQL server for Google Cloud Functions", "keywords": [ "GraphQL", diff --git a/packages/apollo-server-cloudflare/package.json b/packages/apollo-server-cloudflare/package.json index 30918c97fa1..be82a16871c 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.11.0-alpha.1", + "version": "2.11.0-alpha.2", "description": "Production-ready Node.js GraphQL server for Cloudflare workers", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-core/package.json b/packages/apollo-server-core/package.json index 2f11f68731a..8004a617fef 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.11.0-alpha.1", + "version": "2.11.0-alpha.2", "description": "Core engine for Apollo GraphQL server", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-express/package.json b/packages/apollo-server-express/package.json index c3311530e1c..d7c6059bdd7 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.11.0-alpha.1", + "version": "2.11.0-alpha.2", "description": "Production-ready Node.js GraphQL server for Express and Connect", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-fastify/package.json b/packages/apollo-server-fastify/package.json index 6b8a14579b3..8a287ae75ae 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.11.0-alpha.1", + "version": "2.11.0-alpha.2", "description": "Production-ready Node.js GraphQL server for Fastify", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-hapi/package.json b/packages/apollo-server-hapi/package.json index 4967e78c328..7dab11af9b9 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.11.0-alpha.1", + "version": "2.11.0-alpha.2", "description": "Production-ready Node.js GraphQL server for Hapi", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-integration-testsuite/package.json b/packages/apollo-server-integration-testsuite/package.json index 2275cee755d..d31141a291d 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.11.0-alpha.1", + "version": "2.11.0-alpha.2", "description": "Apollo Server Integrations testsuite", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-koa/package.json b/packages/apollo-server-koa/package.json index 7703464f1aa..d6bcb91113a 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.11.0-alpha.1", + "version": "2.11.0-alpha.2", "description": "Production-ready Node.js GraphQL server for Koa", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-lambda/package.json b/packages/apollo-server-lambda/package.json index cd409f4549f..2daf2326a32 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.11.0-alpha.1", + "version": "2.11.0-alpha.2", "description": "Production-ready Node.js GraphQL server for AWS Lambda", "keywords": [ "GraphQL", diff --git a/packages/apollo-server-micro/package.json b/packages/apollo-server-micro/package.json index 409e332749c..74b5f4cbfef 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.11.0-alpha.1", + "version": "2.11.0-alpha.2", "description": "Production-ready Node.js GraphQL server for Micro", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-testing/package.json b/packages/apollo-server-testing/package.json index ddfd3808451..db0d3b3178d 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.11.0-alpha.1", + "version": "2.11.0-alpha.2", "description": "Test utils for apollo-server", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server/package.json b/packages/apollo-server/package.json index 11fa129050d..62d7c56af09 100644 --- a/packages/apollo-server/package.json +++ b/packages/apollo-server/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server", - "version": "2.11.0-alpha.1", + "version": "2.11.0-alpha.2", "description": "Production ready GraphQL Server", "author": "opensource@apollographql.com", "main": "dist/index.js", From ce5ac5620765e208b33076e43c3c85aecca5a4e6 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Tue, 25 Feb 2020 18:19:03 +0200 Subject: [PATCH 25/27] Same/similar to daac9ee06f159f111fc9fc375d965bbdec0db125 in #3829. ...except in a different place. cc @trevor-scheer --- packages/apollo-gateway/src/loadServicesFromStorage.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/apollo-gateway/src/loadServicesFromStorage.ts b/packages/apollo-gateway/src/loadServicesFromStorage.ts index 31c18c5aab7..89d8f5bf260 100644 --- a/packages/apollo-gateway/src/loadServicesFromStorage.ts +++ b/packages/apollo-gateway/src/loadServicesFromStorage.ts @@ -1,4 +1,4 @@ -import { Fetcher } from 'make-fetch-happen'; +import { fetch } from 'apollo-server-env'; import { parse } from 'graphql'; import { Experimental_UpdateServiceDefinitions } from '.'; @@ -61,7 +61,7 @@ export async function getServiceDefinitionsFromStorage({ apiKeyHash: string; graphVariant?: string; federationVersion: number; - fetcher: Fetcher; + fetcher: typeof fetch; }): ReturnType { // fetch the storage secret const storageSecretUrl = getStorageSecretUrl(graphId, apiKeyHash); From 062e6a3a76f00c6f9c5253c1db00f77ba4a74ae4 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Tue, 25 Feb 2020 18:20:03 +0200 Subject: [PATCH 26/27] Release - @apollo/federation@0.13.2-alpha.3 - @apollo/gateway@0.13.2-alpha.3 - apollo-server-azure-functions@2.11.0-alpha.3 - apollo-server-cloud-functions@2.11.0-alpha.3 - apollo-server-cloudflare@2.11.0-alpha.3 - apollo-server-core@2.11.0-alpha.3 - apollo-server-express@2.11.0-alpha.3 - apollo-server-fastify@2.11.0-alpha.3 - apollo-server-hapi@2.11.0-alpha.3 - apollo-server-integration-testsuite@2.11.0-alpha.3 - apollo-server-koa@2.11.0-alpha.3 - apollo-server-lambda@2.11.0-alpha.3 - apollo-server-micro@2.11.0-alpha.3 - apollo-server-testing@2.11.0-alpha.3 - apollo-server@2.11.0-alpha.3 --- packages/apollo-federation/package.json | 2 +- packages/apollo-gateway/package.json | 2 +- packages/apollo-server-azure-functions/package.json | 2 +- packages/apollo-server-cloud-functions/package.json | 2 +- packages/apollo-server-cloudflare/package.json | 2 +- packages/apollo-server-core/package.json | 2 +- packages/apollo-server-express/package.json | 2 +- packages/apollo-server-fastify/package.json | 2 +- packages/apollo-server-hapi/package.json | 2 +- packages/apollo-server-integration-testsuite/package.json | 2 +- packages/apollo-server-koa/package.json | 2 +- packages/apollo-server-lambda/package.json | 2 +- packages/apollo-server-micro/package.json | 2 +- packages/apollo-server-testing/package.json | 2 +- packages/apollo-server/package.json | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/apollo-federation/package.json b/packages/apollo-federation/package.json index dcaed4fc996..4f1df8a823f 100644 --- a/packages/apollo-federation/package.json +++ b/packages/apollo-federation/package.json @@ -1,6 +1,6 @@ { "name": "@apollo/federation", - "version": "0.13.2-alpha.2", + "version": "0.13.2-alpha.3", "description": "Apollo Federation Utilities", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-gateway/package.json b/packages/apollo-gateway/package.json index d9311008c0e..ea8bb5e0406 100644 --- a/packages/apollo-gateway/package.json +++ b/packages/apollo-gateway/package.json @@ -1,6 +1,6 @@ { "name": "@apollo/gateway", - "version": "0.13.2-alpha.2", + "version": "0.13.2-alpha.3", "description": "Apollo Gateway", "author": "opensource@apollographql.com", "main": "dist/index.js", diff --git a/packages/apollo-server-azure-functions/package.json b/packages/apollo-server-azure-functions/package.json index a9935e885f1..e28c7dca5bc 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.11.0-alpha.2", + "version": "2.11.0-alpha.3", "description": "Production-ready Node.js GraphQL server for Azure Functions", "keywords": [ "GraphQL", diff --git a/packages/apollo-server-cloud-functions/package.json b/packages/apollo-server-cloud-functions/package.json index 54ff913e994..8a5bcd4e6f9 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.11.0-alpha.2", + "version": "2.11.0-alpha.3", "description": "Production-ready Node.js GraphQL server for Google Cloud Functions", "keywords": [ "GraphQL", diff --git a/packages/apollo-server-cloudflare/package.json b/packages/apollo-server-cloudflare/package.json index be82a16871c..797c590376a 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.11.0-alpha.2", + "version": "2.11.0-alpha.3", "description": "Production-ready Node.js GraphQL server for Cloudflare workers", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-core/package.json b/packages/apollo-server-core/package.json index 8004a617fef..cf07adf5f9b 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.11.0-alpha.2", + "version": "2.11.0-alpha.3", "description": "Core engine for Apollo GraphQL server", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-express/package.json b/packages/apollo-server-express/package.json index d7c6059bdd7..c0c48e0cb8c 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.11.0-alpha.2", + "version": "2.11.0-alpha.3", "description": "Production-ready Node.js GraphQL server for Express and Connect", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-fastify/package.json b/packages/apollo-server-fastify/package.json index 8a287ae75ae..30805151bee 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.11.0-alpha.2", + "version": "2.11.0-alpha.3", "description": "Production-ready Node.js GraphQL server for Fastify", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-hapi/package.json b/packages/apollo-server-hapi/package.json index 7dab11af9b9..2d0f6a577de 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.11.0-alpha.2", + "version": "2.11.0-alpha.3", "description": "Production-ready Node.js GraphQL server for Hapi", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-integration-testsuite/package.json b/packages/apollo-server-integration-testsuite/package.json index d31141a291d..114522c434a 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.11.0-alpha.2", + "version": "2.11.0-alpha.3", "description": "Apollo Server Integrations testsuite", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-koa/package.json b/packages/apollo-server-koa/package.json index d6bcb91113a..6979ed539ec 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.11.0-alpha.2", + "version": "2.11.0-alpha.3", "description": "Production-ready Node.js GraphQL server for Koa", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-lambda/package.json b/packages/apollo-server-lambda/package.json index 2daf2326a32..defea3bed0b 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.11.0-alpha.2", + "version": "2.11.0-alpha.3", "description": "Production-ready Node.js GraphQL server for AWS Lambda", "keywords": [ "GraphQL", diff --git a/packages/apollo-server-micro/package.json b/packages/apollo-server-micro/package.json index 74b5f4cbfef..34f6e487eb1 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.11.0-alpha.2", + "version": "2.11.0-alpha.3", "description": "Production-ready Node.js GraphQL server for Micro", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server-testing/package.json b/packages/apollo-server-testing/package.json index db0d3b3178d..74c1320b01c 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.11.0-alpha.2", + "version": "2.11.0-alpha.3", "description": "Test utils for apollo-server", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apollo-server/package.json b/packages/apollo-server/package.json index 62d7c56af09..326014a36d5 100644 --- a/packages/apollo-server/package.json +++ b/packages/apollo-server/package.json @@ -1,6 +1,6 @@ { "name": "apollo-server", - "version": "2.11.0-alpha.2", + "version": "2.11.0-alpha.3", "description": "Production ready GraphQL Server", "author": "opensource@apollographql.com", "main": "dist/index.js", From 4155e73ff8541b12944947236cb854d258e08746 Mon Sep 17 00:00:00 2001 From: Trevor Scheer Date: Wed, 26 Feb 2020 17:12:35 -0800 Subject: [PATCH 27/27] Cleanup networkRequests tests * Centralize mocking fixtures and reuse them * Simplify mock request signatures for more easily read and written tests * Create nock/nockSuccess pairs for the most common use case and flexibility cc @abernix --- .../integration/networkRequests.test.ts | 299 +++++------------- .../src/__tests__/integration/nockMocks.ts | 86 +++-- 2 files changed, 142 insertions(+), 243 deletions(-) diff --git a/packages/apollo-gateway/src/__tests__/integration/networkRequests.test.ts b/packages/apollo-gateway/src/__tests__/integration/networkRequests.test.ts index 718e60137d0..ddd11d60b61 100644 --- a/packages/apollo-gateway/src/__tests__/integration/networkRequests.test.ts +++ b/packages/apollo-gateway/src/__tests__/integration/networkRequests.test.ts @@ -1,15 +1,60 @@ import nock from 'nock'; import { ApolloGateway } from '../..'; - import { - mockGetRawPartialSchema, - mockFetchStorageSecret, - mockGetCompositionConfigLink, - mockGetCompositionConfigs, - mockGetImplementingServices, mockLocalhostSDLQuery, + mockStorageSecretSuccess, + mockCompositionConfigLinkSuccess, + mockCompositionConfigsSuccess, + mockImplementingServicesSuccess, + mockRawPartialSchemaSuccess, + apiKeyHash, + graphId, + mockImplementingServices, + mockRawPartialSchema, } from './nockMocks'; +// 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; + +const service = { + implementingServicePath: 'service-definition.json', + partialSchemaPath: 'accounts-partial-schema.json', + federatedServiceURL: 'http://localhost:4001', + federatedServiceSchema: gql` + extend type Query { + me: User + everyone: [User] + } + + "This is my User" + type User @key(fields: "id") { + id: ID! + name: String + username: String + } + ` +}; + +const updatedService = { + implementingServicePath: 'updated-service-definition.json', + partialSchemaPath: 'updated-accounts-partial-schema.json', + federatedServiceURL: 'http://localhost:4002', + federatedServiceSchema: gql` + extend type Query { + me: User + everyone: [User] + } + + "This is my updated User" + type User @key(fields: "id") { + id: ID! + name: String + username: String + } + ` +} + beforeEach(() => { if (!nock.isActive()) nock.activate(); }); @@ -22,246 +67,60 @@ afterEach(() => { }); it('Queries remote endpoints for their SDLs', async () => { - const url = 'http://localhost:4001'; - const sdl = ` - extend type Query { - me: User - everyone: [User] - } - - "My User." - type User @key(fields: "id") { - id: ID! - name: String - username: String - } - `; - - mockLocalhostSDLQuery({ url }).reply(200, { - data: { _service: { sdl } }, + mockLocalhostSDLQuery({ url: service.federatedServiceURL }).reply(200, { + data: { _service: { sdl: service.federatedServiceSchema } }, }); const gateway = new ApolloGateway({ - serviceList: [{ name: 'accounts', url: `${url}/graphql` }], + serviceList: [ + { name: 'accounts', url: `${service.federatedServiceURL}/graphql` }, + ], }); await gateway.load(); - expect(gateway.schema!.getType('User')!.description).toBe('My User.'); + 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 () => { - const serviceName = 'jacksons-service'; - const apiKeyHash = 'abc123'; - - const storageSecret = 'secret'; - const implementingServicePath = - 'path-to-implementing-service-definition.json'; - const partialSchemaPath = 'path-to-accounts-partial-schema.json'; - const federatedServiceName = 'accounts'; - const federatedServiceURL = 'http://localhost:4001'; - const federatedServiceSchema = ` - extend type Query { - me: User - everyone: [User] - } - - "This is my User" - type User @key(fields: "id") { - id: ID! - name: String - username: String - }`; - - mockFetchStorageSecret({ apiKeyHash, serviceName }).reply( - 200, - `"${storageSecret}"`, - ); - - mockGetCompositionConfigLink(storageSecret).reply(200, { - configPath: `${storageSecret}/current/v1/composition-configs/composition-config-path.json`, - }); - - mockGetCompositionConfigs({ - storageSecret, - }).reply(200, { - implementingServiceLocations: [ - { - name: federatedServiceName, - path: `${storageSecret}/current/v1/implementing-services/${federatedServiceName}/${implementingServicePath}`, - }, - ], - }); - - mockGetImplementingServices({ - storageSecret, - implementingServicePath, - federatedServiceName, - }).reply(200, { - name: federatedServiceName, - partialSchemaPath: `${storageSecret}/current/raw-partial-schemas/${partialSchemaPath}`, - url: federatedServiceURL, - }); - - mockGetRawPartialSchema({ - storageSecret, - partialSchemaPath, - }).reply(200, federatedServiceSchema); + mockStorageSecretSuccess(); + mockCompositionConfigLinkSuccess(); + mockCompositionConfigsSuccess([service.implementingServicePath]); + mockImplementingServicesSuccess(service); + mockRawPartialSchemaSuccess(service); const gateway = new ApolloGateway({}); - await gateway.load({ engine: { apiKeyHash, graphId: serviceName } }); + await gateway.load({ engine: { apiKeyHash, graphId } }); expect(gateway.schema!.getType('User')!.description).toBe('This is my User'); }); it('Rollsback to a previous schema when triggered', async () => { - const serviceName = 'jacksons-service'; - const apiKeyHash = 'abc123'; - - const storageSecret = 'secret'; - const implementingServicePath1 = - 'path-to-implementing-service-definition1.json'; - const implementingServicePath2 = - 'path-to-implementing-service-definition2.json'; - const partialSchemaPath1 = 'path-to-accounts-partial-schema1.json'; - const partialSchemaPath2 = 'path-to-accounts-partial-schema2.json'; - const federatedServiceName = 'accounts'; - const federatedServiceURL1 = 'http://localhost:4001'; - const federatedServiceSchema1 = ` - extend type Query { - me: User - everyone: [User] - } - - "This is my User" - type User @key(fields: "id") { - id: ID! - name: String - username: String - }`; - - const federatedServiceURL2 = 'http://localhost:4002'; - const federatedServiceSchema2 = ` - extend type Query { - me: User - everyone: [User] - } - - "This is my User 2" - type User @key(fields: "id") { - id: ID! - name: String - username: String - }`; - // Init - mockFetchStorageSecret({ apiKeyHash, serviceName }).reply( - 200, - `"${storageSecret}"`, - ); - - mockGetCompositionConfigLink(storageSecret).reply(200, { - configPath: `${storageSecret}/current/v1/composition-configs/composition-config-path.json`, - }); - - mockGetCompositionConfigs({ - storageSecret, - }).reply(200, { - implementingServiceLocations: [ - { - name: federatedServiceName, - path: `${storageSecret}/current/v1/implementing-services/${federatedServiceName}/${implementingServicePath1}`, - }, - ], - }); - - mockGetImplementingServices({ - storageSecret, - implementingServicePath: implementingServicePath1, - federatedServiceName, - }).reply(200, { - name: federatedServiceName, - partialSchemaPath: `${storageSecret}/current/raw-partial-schemas/${partialSchemaPath1}`, - url: federatedServiceURL1, - }); - - mockGetRawPartialSchema({ - storageSecret, - partialSchemaPath: partialSchemaPath1, - }).reply(200, federatedServiceSchema1); + mockStorageSecretSuccess(); + mockCompositionConfigLinkSuccess(); + mockCompositionConfigsSuccess([service.implementingServicePath]); + mockImplementingServicesSuccess(service); + mockRawPartialSchemaSuccess(service); // Update 1 - mockFetchStorageSecret({ apiKeyHash, serviceName }).reply( - 200, - `"${storageSecret}"`, - ); - - mockGetCompositionConfigLink(storageSecret).reply(200, { - configPath: `${storageSecret}/current/v1/composition-configs/composition-config-path.json`, - }); - - mockGetCompositionConfigs({ - storageSecret, - }).reply(200, { - implementingServiceLocations: [ - { - name: federatedServiceName, - path: `${storageSecret}/current/v1/implementing-services/${federatedServiceName}/${implementingServicePath2}`, - }, - ], - }); - - mockGetImplementingServices({ - storageSecret, - implementingServicePath: implementingServicePath2, - federatedServiceName, - }).reply(200, { - name: federatedServiceName, - partialSchemaPath: `${storageSecret}/current/raw-partial-schemas/${partialSchemaPath2}`, - url: federatedServiceURL2, - }); - - mockGetRawPartialSchema({ - storageSecret, - partialSchemaPath: partialSchemaPath2, - }).reply(200, federatedServiceSchema2); + mockStorageSecretSuccess(); + mockCompositionConfigLinkSuccess(); + mockCompositionConfigsSuccess([updatedService.implementingServicePath]); + mockImplementingServicesSuccess(updatedService); + mockRawPartialSchemaSuccess(updatedService); // Rollback - mockFetchStorageSecret({ apiKeyHash, serviceName }).reply( - 200, - `"${storageSecret}"`, - ); - - mockGetCompositionConfigLink(storageSecret).reply(200, { - configPath: `${storageSecret}/current/v1/composition-configs/composition-config-path.json`, - }); - - mockGetCompositionConfigs({ - storageSecret, - }).reply(200, { - implementingServiceLocations: [ - { - name: federatedServiceName, - path: `${storageSecret}/current/v1/implementing-services/${federatedServiceName}/${implementingServicePath1}`, - }, - ], - }); - - mockGetImplementingServices({ - storageSecret, - implementingServicePath: implementingServicePath1, - federatedServiceName, - }).reply(304); - - mockGetRawPartialSchema({ - storageSecret, - partialSchemaPath: partialSchemaPath1, - }).reply(304); + mockStorageSecretSuccess(); + mockCompositionConfigLinkSuccess(); + mockCompositionConfigsSuccess([service.implementingServicePath]); + mockImplementingServices(service).reply(304); + mockRawPartialSchema(service).reply(304); jest.useFakeTimers(); const onChange = jest.fn(); const gateway = new ApolloGateway(); - await gateway.load({ engine: { apiKeyHash, graphId: serviceName } }); + await gateway.load({ engine: { apiKeyHash, graphId } }); gateway.onSchemaChange(onChange); // 10000 ms is the default pollInterval diff --git a/packages/apollo-gateway/src/__tests__/integration/nockMocks.ts b/packages/apollo-gateway/src/__tests__/integration/nockMocks.ts index b3c516e7272..8368fbf00a2 100644 --- a/packages/apollo-gateway/src/__tests__/integration/nockMocks.ts +++ b/packages/apollo-gateway/src/__tests__/integration/nockMocks.ts @@ -15,55 +15,95 @@ export const mockLocalhostSDLQuery = ({ url }: { url: string }) => query: 'query GetServiceDefinition { _service { sdl } }', }); -export const mockFetchStorageSecret = ({ - apiKeyHash, - serviceName, -}: { - apiKeyHash: string; - serviceName: string; -}) => +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/${serviceName}/storage-secret/${apiKeyHash}.json`, + `/engine-partial-schema-prod/${graphId}/storage-secret/${apiKeyHash}.json`, ); +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 const mockGetCompositionConfigLink = (storageSecret: string) => +export const mockCompositionConfigLink = () => gcsNock('https://storage.googleapis.com:443').get( `/engine-partial-schema-prod/${storageSecret}/current/v1/composition-config-link`, ); +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 const mockGetCompositionConfigs = ({ - storageSecret, -}: { - storageSecret: string; -}) => +export const mockCompositionConfigs = () => gcsNock('https://storage.googleapis.com:443').get( `/engine-partial-schema-prod/${storageSecret}/current/v1/composition-configs/composition-config-path.json`, ); +export const mockCompositionConfigsSuccess = ( + implementingServicePaths: string[], +) => + mockCompositionConfigs().reply(200, { + implementingServiceLocations: implementingServicePaths.map(servicePath => ({ + name: accountsService, + path: `${storageSecret}/current/v1/implementing-services/${accountsService}/${servicePath}`, + })), + }); + // get implementing service reference, using received composition-config -export const mockGetImplementingServices = ({ - storageSecret, +export const mockImplementingServices = ({ implementingServicePath, - federatedServiceName, }: { - storageSecret: string; implementingServicePath: string; - federatedServiceName: string; }) => gcsNock('https://storage.googleapis.com:443').get( - `/engine-partial-schema-prod/${storageSecret}/current/v1/implementing-services/${federatedServiceName}/${implementingServicePath}`, + `/engine-partial-schema-prod/${storageSecret}/current/v1/implementing-services/${accountsService}/${implementingServicePath}`, ); +export const mockImplementingServicesSuccess = ({ + implementingServicePath, + partialSchemaPath, + federatedServiceURL, +}: { + implementingServicePath: string; + partialSchemaPath: string; + federatedServiceURL: string; +}) => + mockImplementingServices({ + implementingServicePath, + }).reply(200, { + name: accountsService, + partialSchemaPath: `${storageSecret}/current/raw-partial-schemas/${partialSchemaPath}`, + url: federatedServiceURL, + }); + // get raw-partial-schema, using received composition-config -export const mockGetRawPartialSchema = ({ - storageSecret, +export const mockRawPartialSchema = ({ partialSchemaPath, }: { - storageSecret: string; partialSchemaPath: string; }) => gcsNock('https://storage.googleapis.com:443').get( `/engine-partial-schema-prod/${storageSecret}/current/raw-partial-schemas/${partialSchemaPath}`, ); + +export const mockRawPartialSchemaSuccess = ({ + partialSchemaPath, + federatedServiceSchema, +}: { + partialSchemaPath: string; + federatedServiceSchema: string; +}) => + mockRawPartialSchema({ partialSchemaPath }).reply( + 200, + federatedServiceSchema, + );