diff --git a/packages/integration-testsuite/src/apolloServerTests.ts b/packages/integration-testsuite/src/apolloServerTests.ts index 597f208aa32..63761d8b062 100644 --- a/packages/integration-testsuite/src/apolloServerTests.ts +++ b/packages/integration-testsuite/src/apolloServerTests.ts @@ -33,7 +33,7 @@ import type { ApolloServerOptions, ApolloServer, BaseContext, - PluginDefinition, + ApolloServerPlugin, } from '@apollo/server'; import fetch from 'node-fetch'; @@ -541,7 +541,7 @@ export function defineIntegrationTestSuiteApolloServerTests( let serverInstance: ApolloServer; const setupApolloServerAndFetchPairForPlugins = async ( - plugins: PluginDefinition[] = [], + plugins: ApolloServerPlugin[] = [], ) => { const { server, url } = await createServer( { @@ -878,7 +878,7 @@ export function defineIntegrationTestSuiteApolloServerTests( ApolloServerPluginUsageReportingOptions > = {}, constructorOptions: Partial = {}, - plugins: PluginDefinition[] = [], + plugins: ApolloServerPlugin[] = [], ) => { const uri = await createServerAndGetUrl({ typeDefs: gql` diff --git a/packages/plugin-response-cache/src/ApolloServerPluginResponseCache.ts b/packages/plugin-response-cache/src/ApolloServerPluginResponseCache.ts index 6a310e687fa..77def56ccea 100644 --- a/packages/plugin-response-cache/src/ApolloServerPluginResponseCache.ts +++ b/packages/plugin-response-cache/src/ApolloServerPluginResponseCache.ts @@ -174,7 +174,7 @@ export default function plugin( outerRequestContext: GraphQLRequestContext, ): Promise> { const cache = new PrefixingKeyValueCache( - options.cache ?? outerRequestContext.server.cache, + options.cache ?? outerRequestContext.cache, 'fqc:', ); @@ -267,7 +267,7 @@ export default function plugin( }, async willSendResponse(requestContext) { - const logger = requestContext.server.logger || console; + const logger = requestContext.logger || console; if (!isGraphQLQuery(requestContext)) { return; diff --git a/packages/server/src/ApolloServer.ts b/packages/server/src/ApolloServer.ts index 686cd22fb99..127bde0b0c0 100644 --- a/packages/server/src/ApolloServer.ts +++ b/packages/server/src/ApolloServer.ts @@ -42,6 +42,7 @@ import type { PersistedQueryOptions, HTTPGraphQLHead, ContextThunk, + GraphQLRequestContext, } from './externalTypes'; import { runPotentiallyBatchedHttpQuery } from './httpBatching.js'; import { InternalPluginId, pluginIsInternal } from './internalPlugin.js'; @@ -202,20 +203,6 @@ export class ApolloServer { const isDev = nodeEnv !== 'production'; - // Plugins can be (for some reason) provided as a function, which we have to - // call first to get the actual plugin. Note that more plugins can be added - // before `start()` with `addPlugin()` (eg, plugins that want to take this - // ApolloServer as an argument), and `start()` will call - // `ensurePluginInstantiation` to add default plugins. - const plugins: ApolloServerPlugin[] = (config.plugins ?? []).map( - (plugin) => { - if (typeof plugin === 'function') { - return plugin(); - } - return plugin; - }, - ); - const state: ServerState = config.gateway ? // ApolloServer has been initialized but we have not yet tried to load the // schema from the gateway. That will wait until `start()` or @@ -293,7 +280,11 @@ export class ApolloServer { nodeEnv, allowBatchedHttpRequests: config.allowBatchedHttpRequests ?? false, apolloConfig, - plugins, + // Note that more plugins can be added + // before `start()` with `addPlugin()` (eg, plugins that want to take this + // ApolloServer as an argument), and `start()` will call `addDefaultPlugins` + // to add default plugins. + plugins: config.plugins ?? [], parseOptions: config.parseOptions ?? {}, state, stopOnTerminationSignals: config.stopOnTerminationSignals, @@ -377,8 +368,9 @@ export class ApolloServer { }); const schemaDerivedData = schemaManager.getSchemaDerivedData(); - const service: GraphQLServerContext = { - server: this, + const service: GraphQLServerContext = { + logger: this.logger, + cache: this.cache, schema: schemaDerivedData.schema, apollo: this.internals.apolloConfig, startedInBackground, @@ -1154,8 +1146,9 @@ export async function internalExecuteOperation({ const httpGraphQLHead = newHTTPGraphQLHead(); httpGraphQLHead.headers.set('content-type', 'application/json'); - const requestContext = { - server, + const requestContext: GraphQLRequestContext = { + logger: server.logger, + cache: server.cache, schema: schemaDerivedData.schema, request: graphQLRequest, response: { result: {}, http: httpGraphQLHead }, diff --git a/packages/server/src/__tests__/ApolloServer.test.ts b/packages/server/src/__tests__/ApolloServer.test.ts index 9b80637b3fe..95a68b3e2e0 100644 --- a/packages/server/src/__tests__/ApolloServer.test.ts +++ b/packages/server/src/__tests__/ApolloServer.test.ts @@ -489,19 +489,27 @@ describe('ApolloServer executeOperation', () => { }, }; + // A plugin that expects specific fields to be set is not a plugin that + // doesn't promise to set any fields. // @ts-expect-error takesPlugin(specificPlugin); - // @ts-expect-error + // This is OK: plugins only get to read context, not write it, so a plugin + // that reads no interesting fields can be used as a plugin that is + // hypothetically allowed to read some interesting fields but chooses not + // to. takesPlugin(basePlugin); + // You can't use a plugin that expects specific fields to exist with a + // server that doesn't require them to be set when executing operations. new ApolloServer({ typeDefs: 'type Query { x: ID }', // @ts-expect-error plugins: [specificPlugin], }); + // A plugin that doesn't expect any fields to be set works fine with a + // server that sets some fields when executing operations. new ApolloServer({ typeDefs: 'type Query { x: ID }', - // @ts-expect-error plugins: [basePlugin], }); }); diff --git a/packages/server/src/__tests__/logger.test.ts b/packages/server/src/__tests__/logger.test.ts index fb8a1f09ad7..9d7547561e2 100644 --- a/packages/server/src/__tests__/logger.test.ts +++ b/packages/server/src/__tests__/logger.test.ts @@ -15,7 +15,7 @@ describe('logger', () => { `, plugins: [ { - async serverWillStart({ server: { logger } }) { + async serverWillStart({ logger }) { logger.debug(KNOWN_DEBUG_MESSAGE); }, }, @@ -42,7 +42,7 @@ describe('logger', () => { `, plugins: [ { - async serverWillStart({ server: { logger } }) { + async serverWillStart({ logger }) { logger.debug(KNOWN_DEBUG_MESSAGE); }, }, diff --git a/packages/server/src/externalTypes/constructor.ts b/packages/server/src/externalTypes/constructor.ts index cbf6ed44e9a..cc173cb720e 100644 --- a/packages/server/src/externalTypes/constructor.ts +++ b/packages/server/src/externalTypes/constructor.ts @@ -11,7 +11,7 @@ import type { import type { KeyValueCache } from '@apollo/utils.keyvaluecache'; import type { GatewayInterface } from '@apollo/server-gateway-interface'; import type { BaseContext } from '.'; -import type { PluginDefinition } from './plugins'; +import type { ApolloServerPlugin } from './plugins'; export type DocumentStore = KeyValueCache; @@ -86,7 +86,7 @@ interface ApolloServerOptionsBase { allowBatchedHttpRequests?: boolean; introspection?: boolean; - plugins?: PluginDefinition[]; + plugins?: ApolloServerPlugin[]; persistedQueries?: PersistedQueryOptions | false; stopOnTerminationSignals?: boolean; apollo?: ApolloConfigInput; diff --git a/packages/server/src/externalTypes/graphql.ts b/packages/server/src/externalTypes/graphql.ts index bedd8b80308..a032bfd5228 100644 --- a/packages/server/src/externalTypes/graphql.ts +++ b/packages/server/src/externalTypes/graphql.ts @@ -9,7 +9,8 @@ import type { import type { CachePolicy } from '@apollo/cache-control-types'; import type { BaseContext } from './context'; import type { HTTPGraphQLHead, HTTPGraphQLRequest } from './http'; -import type { ApolloServer } from '../ApolloServer'; +import type { Logger } from '@apollo/utils.logger'; +import type { KeyValueCache } from '@apollo/utils.keyvaluecache'; export interface GraphQLRequest { query?: string; @@ -44,7 +45,8 @@ export interface GraphQLRequestMetrics { } export interface GraphQLRequestContext { - readonly server: ApolloServer; + readonly logger: Logger; + readonly cache: KeyValueCache; readonly request: GraphQLRequest; readonly response: GraphQLResponse; diff --git a/packages/server/src/externalTypes/index.ts b/packages/server/src/externalTypes/index.ts index d3cd08d9e6c..c88be02a6c6 100644 --- a/packages/server/src/externalTypes/index.ts +++ b/packages/server/src/externalTypes/index.ts @@ -29,7 +29,6 @@ export type { GraphQLServerListener, GraphQLServerContext, LandingPage, - PluginDefinition, } from './plugins.js'; export type { GraphQLRequestContextDidEncounterErrors, diff --git a/packages/server/src/externalTypes/plugins.ts b/packages/server/src/externalTypes/plugins.ts index 6a023bd7b8b..bea25f684ab 100644 --- a/packages/server/src/externalTypes/plugins.ts +++ b/packages/server/src/externalTypes/plugins.ts @@ -1,5 +1,6 @@ +import type { KeyValueCache } from '@apollo/utils.keyvaluecache'; +import type { Logger } from '@apollo/utils.logger'; import type { GraphQLResolveInfo, GraphQLSchema } from 'graphql'; -import type { ApolloServer } from '../ApolloServer'; import type { ApolloConfig } from './constructor'; import type { BaseContext } from './context'; import type { GraphQLRequestContext, GraphQLResponse } from './graphql'; @@ -14,8 +15,10 @@ import type { GraphQLRequestContextWillSendResponse, } from './requestPipeline'; -export interface GraphQLServerContext { - server: ApolloServer; +export interface GraphQLServerContext { + readonly logger: Logger; + readonly cache: KeyValueCache; + schema: GraphQLSchema; apollo: ApolloConfig; // TODO(AS4): Make sure we document that we removed `persistedQueries`. @@ -28,15 +31,11 @@ export interface GraphQLSchemaContext { coreSupergraphSdl?: string; } -// A plugin can return an interface that matches `ApolloServerPlugin`, or a -// factory function that returns `ApolloServerPlugin`. -export type PluginDefinition = - | ApolloServerPlugin - | (() => ApolloServerPlugin); - -export interface ApolloServerPlugin { +export interface ApolloServerPlugin< + in TContext extends BaseContext = BaseContext, +> { serverWillStart?( - service: GraphQLServerContext, + service: GraphQLServerContext, ): Promise; requestDidStart?( diff --git a/packages/server/src/plugin/cacheControl/index.ts b/packages/server/src/plugin/cacheControl/index.ts index bb94c65af22..eb8f4494245 100644 --- a/packages/server/src/plugin/cacheControl/index.ts +++ b/packages/server/src/plugin/cacheControl/index.ts @@ -1,4 +1,4 @@ -import type { ApolloServerPlugin, BaseContext } from '../../externalTypes'; +import type { ApolloServerPlugin } from '../../externalTypes'; import { DirectiveNode, getNamedType, @@ -47,9 +47,9 @@ export interface ApolloServerPluginCacheControlOptions { __testing__cacheHints?: Map; } -export function ApolloServerPluginCacheControl( +export function ApolloServerPluginCacheControl( options: ApolloServerPluginCacheControlOptions = Object.create(null), -): ApolloServerPlugin { +): ApolloServerPlugin { let typeAnnotationCache: LRUCache; let fieldAnnotationCache: LRUCache< diff --git a/packages/server/src/plugin/disabled/index.ts b/packages/server/src/plugin/disabled/index.ts index ddc1e8212b1..67327e34b01 100644 --- a/packages/server/src/plugin/disabled/index.ts +++ b/packages/server/src/plugin/disabled/index.ts @@ -6,10 +6,8 @@ import type { InternalPluginId, } from '../../internalPlugin'; -function disabledPlugin( - id: InternalPluginId, -): ApolloServerPlugin { - const plugin: InternalApolloServerPlugin = { +function disabledPlugin(id: InternalPluginId): ApolloServerPlugin { + const plugin: InternalApolloServerPlugin = { __internal_plugin_id__() { return id; }, @@ -17,26 +15,18 @@ function disabledPlugin( return plugin; } -export function ApolloServerPluginCacheControlDisabled< - TContext extends BaseContext, ->(): ApolloServerPlugin { +export function ApolloServerPluginCacheControlDisabled(): ApolloServerPlugin { return disabledPlugin('CacheControl'); } -export function ApolloServerPluginInlineTraceDisabled< - TContext extends BaseContext, ->(): ApolloServerPlugin { +export function ApolloServerPluginInlineTraceDisabled(): ApolloServerPlugin { return disabledPlugin('InlineTrace'); } -export function ApolloServerPluginLandingPageDisabled< - TContext extends BaseContext, ->(): ApolloServerPlugin { +export function ApolloServerPluginLandingPageDisabled(): ApolloServerPlugin { return disabledPlugin('LandingPageDisabled'); } -export function ApolloServerPluginUsageReportingDisabled< - TContext extends BaseContext, ->(): ApolloServerPlugin { +export function ApolloServerPluginUsageReportingDisabled(): ApolloServerPlugin { return disabledPlugin('UsageReporting'); } diff --git a/packages/server/src/plugin/drainHttpServer/index.ts b/packages/server/src/plugin/drainHttpServer/index.ts index fa237f14a20..287e71ecd84 100644 --- a/packages/server/src/plugin/drainHttpServer/index.ts +++ b/packages/server/src/plugin/drainHttpServer/index.ts @@ -1,5 +1,5 @@ import type http from 'http'; -import type { ApolloServerPlugin, BaseContext } from '../../externalTypes'; +import type { ApolloServerPlugin } from '../../externalTypes'; import { Stopper } from './stoppable.js'; /** @@ -23,9 +23,9 @@ export interface ApolloServerPluginDrainHttpServerOptions { * See https://www.apollographql.com/docs/apollo-server/api/plugin/drain-http-server/ * for details. */ -export function ApolloServerPluginDrainHttpServer( +export function ApolloServerPluginDrainHttpServer( options: ApolloServerPluginDrainHttpServerOptions, -): ApolloServerPlugin { +): ApolloServerPlugin { const stopper = new Stopper(options.httpServer); return { async serverWillStart() { diff --git a/packages/server/src/plugin/inlineTrace/index.ts b/packages/server/src/plugin/inlineTrace/index.ts index 410b81f0c04..ffab54233da 100644 --- a/packages/server/src/plugin/inlineTrace/index.ts +++ b/packages/server/src/plugin/inlineTrace/index.ts @@ -3,7 +3,7 @@ import { TraceTreeBuilder } from '../traceTreeBuilder.js'; import type { SendErrorsOptions } from '../usageReporting/index.js'; import { internalPlugin } from '../../internalPlugin.js'; import { schemaIsFederated } from '../schemaIsFederated.js'; -import type { ApolloServerPlugin, BaseContext } from '../../externalTypes'; +import type { ApolloServerPlugin } from '../../externalTypes'; export interface ApolloServerPluginInlineTraceOptions { /** @@ -44,15 +44,15 @@ export interface ApolloServerPluginInlineTraceOptions { // on the `extensions`.`ftv1` property of the response. The Apollo Gateway // utilizes this data to construct the full trace and submit it to Apollo's // usage reporting ingress. -export function ApolloServerPluginInlineTrace( +export function ApolloServerPluginInlineTrace( options: ApolloServerPluginInlineTraceOptions = Object.create(null), -): ApolloServerPlugin { +): ApolloServerPlugin { let enabled: boolean | null = options.__onlyIfSchemaIsFederated ? null : true; return internalPlugin({ __internal_plugin_id__() { return 'InlineTrace'; }, - async serverWillStart({ schema, server }) { + async serverWillStart({ schema, logger }) { // Handle the case that the plugin was implicitly installed. We only want it // to actually be active if the schema appears to be federated. If you don't // like the log line, just install `ApolloServerPluginInlineTrace()` in @@ -60,7 +60,7 @@ export function ApolloServerPluginInlineTrace( if (enabled === null) { enabled = schemaIsFederated(schema); if (enabled) { - server.logger.info( + logger.info( 'Enabling inline tracing for this federated service. To disable, use ' + 'ApolloServerPluginInlineTraceDisabled.', ); diff --git a/packages/server/src/plugin/landingPage/default/index.ts b/packages/server/src/plugin/landingPage/default/index.ts index e0cc1841340..6b80ca367e5 100644 --- a/packages/server/src/plugin/landingPage/default/index.ts +++ b/packages/server/src/plugin/landingPage/default/index.ts @@ -15,11 +15,9 @@ export type { ApolloServerPluginLandingPageProductionDefaultOptions, }; -export function ApolloServerPluginLandingPageLocalDefault< - TContext extends BaseContext, ->( +export function ApolloServerPluginLandingPageLocalDefault( options: ApolloServerPluginLandingPageLocalDefaultOptions = {}, -): ApolloServerPlugin { +): ApolloServerPlugin { const { version, __internal_apolloStudioEnv__, ...rest } = { // we default to Sandbox unless embed is specified as false embed: true as const, @@ -32,11 +30,9 @@ export function ApolloServerPluginLandingPageLocalDefault< }); } -export function ApolloServerPluginLandingPageProductionDefault< - TContext extends BaseContext, ->( +export function ApolloServerPluginLandingPageProductionDefault( options: ApolloServerPluginLandingPageProductionDefaultOptions = {}, -): ApolloServerPlugin { +): ApolloServerPlugin { const { version, __internal_apolloStudioEnv__, ...rest } = options; return ApolloServerPluginLandingPageDefault(version, { isProd: true, diff --git a/packages/server/src/plugin/landingPage/graphqlPlayground/index.ts b/packages/server/src/plugin/landingPage/graphqlPlayground/index.ts index dd4d57bcbb6..2f54902cf1e 100644 --- a/packages/server/src/plugin/landingPage/graphqlPlayground/index.ts +++ b/packages/server/src/plugin/landingPage/graphqlPlayground/index.ts @@ -7,7 +7,6 @@ import { renderPlaygroundPage } from '@apollographql/graphql-playground-html'; import type { ApolloServerPlugin, - BaseContext, GraphQLServerListener, } from '../../../externalTypes'; @@ -27,13 +26,11 @@ export type ApolloServerPluginLandingPageGraphQLPlaygroundOptions = Parameters< typeof renderPlaygroundPage >[0]; -export function ApolloServerPluginLandingPageGraphQLPlayground< - TContext extends BaseContext, ->( +export function ApolloServerPluginLandingPageGraphQLPlayground( options: ApolloServerPluginLandingPageGraphQLPlaygroundOptions = Object.create( null, ), -): ApolloServerPlugin { +): ApolloServerPlugin { return { async serverWillStart(): Promise { return { diff --git a/packages/server/src/plugin/schemaReporting/index.ts b/packages/server/src/plugin/schemaReporting/index.ts index 5ad21e37724..8c79ada7d6d 100644 --- a/packages/server/src/plugin/schemaReporting/index.ts +++ b/packages/server/src/plugin/schemaReporting/index.ts @@ -5,7 +5,7 @@ import { printSchema, validateSchema, buildSchema } from 'graphql'; import { SchemaReporter } from './schemaReporter.js'; import { schemaIsFederated } from '../schemaIsFederated.js'; import type { SchemaReport } from './generated/operations'; -import type { ApolloServerPlugin, BaseContext } from '../../externalTypes'; +import type { ApolloServerPlugin } from '../../externalTypes'; import type { Fetcher } from '@apollo/utils.fetcher'; import { packageVersion } from '../../generated/packageVersion.js'; import { computeCoreSchemaHash } from '../../utils/computeCoreSchemaHash.js'; @@ -58,23 +58,22 @@ export interface ApolloServerPluginSchemaReportingOptions { fetcher?: Fetcher; } -export function ApolloServerPluginSchemaReporting( +export function ApolloServerPluginSchemaReporting( { initialDelayMaxMs, overrideReportedSchema, endpointUrl, fetcher, }: ApolloServerPluginSchemaReportingOptions = Object.create(null), -): ApolloServerPlugin { +): ApolloServerPlugin { const bootId = uuidv4(); return internalPlugin({ __internal_plugin_id__() { return 'SchemaReporting'; }, - async serverWillStart({ apollo, schema, server }) { + async serverWillStart({ apollo, schema, logger }) { const { key, graphRef } = apollo; - const { logger } = server; if (!key) { throw Error( 'To use ApolloServerPluginSchemaReporting, you must provide an Apollo API ' + diff --git a/packages/server/src/plugin/usageReporting/plugin.ts b/packages/server/src/plugin/usageReporting/plugin.ts index a8fc2d68fed..d7ceb404f16 100644 --- a/packages/server/src/plugin/usageReporting/plugin.ts +++ b/packages/server/src/plugin/usageReporting/plugin.ts @@ -105,12 +105,12 @@ export function ApolloServerPluginUsageReporting( }, async serverWillStart({ - server, + logger: serverLogger, apollo, startedInBackground, }): Promise { // Use the plugin-specific logger if one is provided; otherwise the general server one. - const logger = options.logger ?? server.logger; + const logger = options.logger ?? serverLogger; const { key, graphRef } = apollo; if (!(key && graphRef)) { throw new Error( @@ -386,7 +386,6 @@ export function ApolloServerPluginUsageReporting( schema, request: { http, variables }, }): GraphQLRequestListener => { - const logger = options.logger ?? server.logger; const treeBuilder: TraceTreeBuilder = new TraceTreeBuilder({ maskedBy: 'ApolloServerPluginUsageReporting', sendErrors: options.sendErrorsInTraces,