diff --git a/CHANGELOG.md b/CHANGELOG.md index b0beee78ae8..d6e4fe3962d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The version headers in this history reflect the versions of Apollo Server itself - `apollo-server-core`: If a client does not provide a value or provides null for a variable declared to be non-null, this is now reported as an error with an `extensions.code` of `BAD_USER_INPUT` rather than `INTERNAL_SERVER_ERROR`. (This is similar to a change we made in v2.23.0 for variables that are sent as the wrong type.) [PR #5508](https://github.com/apollographql/apollo-server/pull/5508) [Issue #5353](https://github.com/apollographql/apollo-server/issues/5353) - `apollo-server-core`/`apollo-server-plugin-base`: Add support for `schemaDidLoadOrUpdate` event hooks, to be specified by the `serverWillStart` event hook. Plugins listening for this event will receive the API schema (and core schema for gateways) when the server's schema is initially loaded and when the server's schema is updated. For more information about this plugin event, see [the plugin event reference documentation](https://www.apollographql.com/docs/apollo-server/integrations/plugins-event-reference/). [PR #5187](https://github.com/apollographql/apollo-server/pull/5187) - `apollo-server-core`: Add support for schema reporting when using Apollo Gateway. At the time of this package's release, Apollo Studio does not yet support schema reporting from gateways, so you should not use this feature yet for gateways (unless instructed otherwise by Apollo staff or by the Studio docs). If you do enable schema reporting for a gateway, the version of `@apollo/gateway` must be at least `0.35.0` , or else `start()` will error. [PR #5187](https://github.com/apollographql/apollo-server/pull/5187) +- `apollo-server-core`: Support gateways without executors, to help with mocking gateways. See the PR for details. [PR #FIXME](https://github.com/apollographql/apollo-server/pull/FIXME) ## v3.0.2 diff --git a/packages/apollo-server-core/src/ApolloServer.ts b/packages/apollo-server-core/src/ApolloServer.ts index e0022178250..808db6765da 100644 --- a/packages/apollo-server-core/src/ApolloServer.ts +++ b/packages/apollo-server-core/src/ApolloServer.ts @@ -281,14 +281,6 @@ export class ApolloServerBase< logger: this.logger, }), }; - - // The main thing that the Gateway does is replace execution with - // its own executor. It would be awkward if you always had to pass - // `gateway: gateway, executor: gateway` to this constructor, so - // we let specifying `gateway` be a shorthand for the above. - // (We won't actually invoke the executor until after we're successfully - // called `gateway.load`.) - this.requestOptions.executor = gateway.executor; } else { // We construct the schema synchronously so that we can fail fast if the // schema can't be constructed. (This used to be more important because we @@ -376,10 +368,15 @@ export class ApolloServerBase< schemaManager, }; try { - await schemaManager.start(); + const executor = await schemaManager.start(); this.toDispose.add(async () => { await schemaManager.stop(); }); + if (executor) { + // If we loaded an executor from a gateway, use it to execute + // operations. + this.requestOptions.executor = executor; + } const schemaDerivedData = schemaManager.getSchemaDerivedData(); const service: GraphQLServiceContext = { diff --git a/packages/apollo-server-core/src/types.ts b/packages/apollo-server-core/src/types.ts index ccaad86441b..bb9fd052d1d 100644 --- a/packages/apollo-server-core/src/types.ts +++ b/packages/apollo-server-core/src/types.ts @@ -5,8 +5,6 @@ import type { ApolloConfig, ValueOrPromise, GraphQLExecutor, - GraphQLExecutionResult, - GraphQLRequestContextExecutionDidStart, ApolloConfigInput, } from 'apollo-server-types'; @@ -50,7 +48,7 @@ export type SchemaChangeCallback = (apiSchema: GraphQLSchema) => void; export type GraphQLServiceConfig = { schema: GraphQLSchema; - executor: GraphQLExecutor; + executor: GraphQLExecutor | null; }; export interface GatewayInterface { @@ -71,13 +69,12 @@ export interface GatewayInterface { }) => void, ): Unsubscriber; - // Note: The `TContext` typing here is not conclusively behaving as we expect: - // https://github.com/apollographql/apollo-server/pull/3811#discussion_r387381605 - executor( - requestContext: GraphQLRequestContextExecutionDidStart, - ): Promise; - stop(): Promise; + + // Note: this interface used to have an `executor` method, and also return the + // executor from `load()`. ApolloServer would only use the former. We dropped + // this method and now use the latter, which allows you to make a "mock + // gateway" that updates the schema over time but uses normal execution. } // This was the name used for GatewayInterface in AS2; continue to export it so diff --git a/packages/apollo-server-core/src/utils/schemaManager.ts b/packages/apollo-server-core/src/utils/schemaManager.ts index 82e9a003b84..5519a347583 100644 --- a/packages/apollo-server-core/src/utils/schemaManager.ts +++ b/packages/apollo-server-core/src/utils/schemaManager.ts @@ -1,6 +1,7 @@ import { GraphQLSchema } from 'graphql'; import { ApolloConfig, + GraphQLExecutor, GraphQLSchemaContext, Logger, } from 'apollo-server-types'; @@ -81,8 +82,9 @@ export class SchemaManager { * - Initialize schema-derived data. * - Synchronously notify onSchemaLoadOrUpdate() listeners of schema load, and * asynchronously notify them of schema updates. + * - If we started a gateway, returns the gateway's executor; otherwise null. */ - public async start(): Promise { + public async start(): Promise { if (this.modeSpecificState.mode === 'gateway') { const gateway = this.modeSpecificState.gateway; if (gateway.onSchemaLoadOrUpdate) { @@ -115,10 +117,12 @@ export class SchemaManager { if (!this.schemaDerivedData) { this.processSchemaLoadOrUpdateEvent({ apiSchema: config.schema }); } + return config.executor; } else { this.processSchemaLoadOrUpdateEvent({ apiSchema: this.modeSpecificState.apiSchema, }); + return null; } } diff --git a/packages/apollo-server-integration-testsuite/src/ApolloServer.ts b/packages/apollo-server-integration-testsuite/src/ApolloServer.ts index 0b66411326d..ed4d9f4776e 100644 --- a/packages/apollo-server-integration-testsuite/src/ApolloServer.ts +++ b/packages/apollo-server-integration-testsuite/src/ApolloServer.ts @@ -36,7 +36,6 @@ import { ApolloServerBase, PluginDefinition, GatewayInterface, - GraphQLExecutor, GraphQLServiceConfig, ApolloServerPluginInlineTrace, ApolloServerPluginUsageReporting, @@ -126,11 +125,9 @@ const schema = new GraphQLSchema({ const makeGatewayMock = ({ optionsSpy = (_options) => {}, unsubscribeSpy = () => {}, - executor = async () => ({}), }: { optionsSpy?: (_options: any) => void; unsubscribeSpy?: () => void; - executor?: GraphQLExecutor; } = {}) => { let resolution: GraphQLServiceConfig | null = null; let rejection: Error | null = null; @@ -145,7 +142,6 @@ const makeGatewayMock = ({ }; const mockedGateway: GatewayInterface = { - executor, load: async (options) => { optionsSpy(options); // Make sure it's async @@ -414,7 +410,7 @@ export function testApolloServer( Promise.resolve({ data: { testString: 'hi - but federated!' } }), ); - const { gateway, triggers } = makeGatewayMock({ executor }); + const { gateway, triggers } = makeGatewayMock(); triggers.resolveLoad({ schema, executor }); @@ -2701,7 +2697,7 @@ export function testApolloServer( ? Promise.resolve({ data: { testString1: 'hello' } }) : Promise.resolve({ data: { testString2: 'aloha' } }); - const { gateway, triggers } = makeGatewayMock({ executor }); + const { gateway, triggers } = makeGatewayMock(); triggers.resolveLoad({ schema: makeQueryTypeWithField('testString1'), @@ -2782,7 +2778,7 @@ export function testApolloServer( return { data: { testString: 'hi - but federated!' } }; }; - const { gateway, triggers } = makeGatewayMock({ executor }); + const { gateway, triggers } = makeGatewayMock(); triggers.resolveLoad({ schema, executor }); const { url: uri } = await createApolloServer({ @@ -2844,7 +2840,7 @@ export function testApolloServer( return { data: { [`testString${i}`]: `${i}` } }; }; - const { gateway, triggers } = makeGatewayMock({ executor }); + const { gateway, triggers } = makeGatewayMock(); triggers.resolveLoad({ schema: makeQueryTypeWithField('testString1'),