diff --git a/packages/apollo-server-core/src/__tests__/runQuery.test.ts b/packages/apollo-server-core/src/__tests__/runQuery.test.ts index 69f50590d3f..e9a5f1915c5 100644 --- a/packages/apollo-server-core/src/__tests__/runQuery.test.ts +++ b/packages/apollo-server-core/src/__tests__/runQuery.test.ts @@ -399,6 +399,27 @@ describe('runQuery', () => { } } + it('warns that they are deprecated', async () => { + const queryString = `{ testString }`; + const logger = { + warn: jest.fn(console.warn), + info: console.info, + debug: console.debug, + error: console.error, + }; + const extensions = [() => new CustomExtension()]; + await runQuery({ + schema, + queryString, + extensions, + request: new MockReq(), + }, { + logger, + }); + expect(logger.warn).toHaveBeenCalledWith( + expect.stringMatching(/^\[deprecated\] A "CustomExtension" was/)); + }); + it('creates the extension stack', async () => { const queryString = `{ testString }`; const extensions = [() => new CustomExtension()]; diff --git a/packages/apollo-server-core/src/requestPipeline.ts b/packages/apollo-server-core/src/requestPipeline.ts index 09d4d4517fe..5398ce6f79e 100644 --- a/packages/apollo-server-core/src/requestPipeline.ts +++ b/packages/apollo-server-core/src/requestPipeline.ts @@ -113,6 +113,14 @@ export type DataSources = { type Mutable = { -readonly [P in keyof T]: T[P] }; +/** + * We attach this symbol to the constructor of extensions to mark that we've + * already warned about the deprecation of the `graphql-extensions` API for that + * particular definition. + */ +const symbolExtensionDeprecationDone = + Symbol("apolloServerExtensionDeprecationDone"); + export async function processGraphQLRequest( config: GraphQLRequestPipelineConfig, requestContext: Mutable>, @@ -634,6 +642,39 @@ export async function processGraphQLRequest( // objects. const extensions = config.extensions ? config.extensions.map(f => f()) : []; + // Warn about usage of (deprecated) `graphql-extensions` implementations. + // Since extensions are often provided as factory functions which + // instantiate an extension on each request, we'll attach a symbol to the + // constructor after we've warned to ensure that we don't do it on each + // request. Another option here might be to keep a `Map` of constructor + // instances within this module, but I hope this will do the trick. + const hasOwn = Object.prototype.hasOwnProperty; + extensions.forEach((extension) => { + // Using `hasOwn` just in case there is a user-land `hasOwnProperty` + // defined on the `constructor` object. + if (hasOwn.call(extension.constructor, symbolExtensionDeprecationDone)) { + return; + } + + Object.defineProperty( + extension.constructor, + symbolExtensionDeprecationDone, + { value: true } + ); + + const extensionName = extension.constructor.name; + logger.warn( + '[deprecated] ' + + ('A "' + extensionName + '" ' || 'An anonymous extension ') + + 'was defined within the "extensions" configuration for ' + + 'Apollo Server. The API on which this extension is built ' + + '("graphql-extensions") is being deprecated in the next major ' + + 'version of Apollo Server in favor of the new plugin API. See ' + + 'https://go.apollo.dev/s/plugins for the documentation on how ' + + 'these plugins are to be defined and used.', + ); + }); + return new GraphQLExtensionStack(extensions); }