diff --git a/packages/server/src/ApolloServer.ts b/packages/server/src/ApolloServer.ts index c2857ec3d9a..76d279f11fa 100644 --- a/packages/server/src/ApolloServer.ts +++ b/packages/server/src/ApolloServer.ts @@ -1213,6 +1213,12 @@ export async function internalExecuteOperation({ return requestContext.response; } +// Unlike InternalPlugins (where we can decide whether to install the default +// plugin based on looking at which plugins are installed), +// ImplicitlyInstallablePlugins (ie the default landing page plugin) can't +// determine if they're needed until later in startup. Specifically, we can't +// know if we've defined our own landing page until after serverWillStart +// plugins have run. export type ImplicitlyInstallablePlugin = ApolloServerPlugin & { __internal_installed_implicitly__: boolean; diff --git a/packages/server/src/__tests__/plugin/schemaReporting/computeCoreSchemaHash.test.ts b/packages/server/src/__tests__/utils/computeCoreSchemaHash.test.ts similarity index 94% rename from packages/server/src/__tests__/plugin/schemaReporting/computeCoreSchemaHash.test.ts rename to packages/server/src/__tests__/utils/computeCoreSchemaHash.test.ts index a70f15541f0..a52c692480d 100644 --- a/packages/server/src/__tests__/plugin/schemaReporting/computeCoreSchemaHash.test.ts +++ b/packages/server/src/__tests__/utils/computeCoreSchemaHash.test.ts @@ -1,5 +1,5 @@ -import { computeCoreSchemaHash } from '../../../plugin/schemaReporting'; import { printSchema, buildSchema } from 'graphql'; +import { computeCoreSchemaHash } from '../../utils/computeCoreSchemaHash.js'; describe('Executable Schema Id', () => { const unsortedGQLSchemaDocument = ` diff --git a/packages/server/src/internalPlugin.ts b/packages/server/src/internalPlugin.ts index 4315267af25..d5d92f90db7 100644 --- a/packages/server/src/internalPlugin.ts +++ b/packages/server/src/internalPlugin.ts @@ -14,6 +14,16 @@ export interface InternalApolloServerPlugin __internal_plugin_id__(): InternalPluginId; } +// Helper function for writing internal plugins which lets you write an object +// that is type-checked as InternalApolloServerPlugin but is still only of type +// ApolloServerPlugin (as appropriate for externally-exported plugin-returning +// functions). +export function internalPlugin( + p: InternalApolloServerPlugin, +): ApolloServerPlugin { + return p; +} + export type InternalPluginId = | 'CacheControl' | 'LandingPageDisabled' diff --git a/packages/server/src/plugin/cacheControl/index.ts b/packages/server/src/plugin/cacheControl/index.ts index 04e593e8284..dc6107dadbb 100644 --- a/packages/server/src/plugin/cacheControl/index.ts +++ b/packages/server/src/plugin/cacheControl/index.ts @@ -1,4 +1,5 @@ import type { + ApolloServerPlugin, BaseContext, CacheAnnotation, CacheHint, @@ -15,7 +16,7 @@ import { responsePathAsArray, } from 'graphql'; import { newCachePolicy } from '../../cachePolicy.js'; -import type { InternalApolloServerPlugin } from '../../internalPlugin'; +import { internalPlugin } from '../../internalPlugin.js'; import LRUCache from 'lru-cache'; export interface ApolloServerPluginCacheControlOptions { @@ -39,7 +40,7 @@ export interface ApolloServerPluginCacheControlOptions { export function ApolloServerPluginCacheControl( options: ApolloServerPluginCacheControlOptions = Object.create(null), -): InternalApolloServerPlugin { +): ApolloServerPlugin { let typeAnnotationCache: LRUCache; let fieldAnnotationCache: LRUCache< @@ -47,7 +48,7 @@ export function ApolloServerPluginCacheControl( CacheAnnotation >; - return { + return internalPlugin({ __internal_plugin_id__() { return 'CacheControl'; }, @@ -267,7 +268,7 @@ export function ApolloServerPluginCacheControl( }, }; }, - }; + }); } function cacheAnnotationFromDirectives( diff --git a/packages/server/src/plugin/inlineTrace/index.ts b/packages/server/src/plugin/inlineTrace/index.ts index 0ab5b8889ec..85c138dbad7 100644 --- a/packages/server/src/plugin/inlineTrace/index.ts +++ b/packages/server/src/plugin/inlineTrace/index.ts @@ -1,9 +1,9 @@ import { Trace } from '@apollo/usage-reporting-protobuf'; import { TraceTreeBuilder } from '../traceTreeBuilder.js'; import type { ApolloServerPluginUsageReportingOptions } from '../usageReporting/options'; -import type { InternalApolloServerPlugin } from '../../internalPlugin'; +import { internalPlugin } from '../../internalPlugin.js'; import { schemaIsFederated } from '../schemaIsFederated.js'; -import type { BaseContext } from '../../externalTypes'; +import type { ApolloServerPlugin, BaseContext } from '../../externalTypes'; export interface ApolloServerPluginInlineTraceOptions { /** @@ -33,9 +33,9 @@ export interface ApolloServerPluginInlineTraceOptions { // usage reporting ingress. export function ApolloServerPluginInlineTrace( options: ApolloServerPluginInlineTraceOptions = Object.create(null), -): InternalApolloServerPlugin { +): ApolloServerPlugin { let enabled: boolean | null = options.__onlyIfSchemaIsFederated ? null : true; - return { + return internalPlugin({ __internal_plugin_id__() { return 'InlineTrace'; }, @@ -128,5 +128,5 @@ export function ApolloServerPluginInlineTrace( }, }; }, - }; + }); } diff --git a/packages/server/src/plugin/landingPage/default/index.ts b/packages/server/src/plugin/landingPage/default/index.ts index 15a87fb6c84..e61047f0ccb 100644 --- a/packages/server/src/plugin/landingPage/default/index.ts +++ b/packages/server/src/plugin/landingPage/default/index.ts @@ -1,4 +1,4 @@ -import type { BaseContext } from '../../../externalTypes'; +import type { ApolloServerPlugin, BaseContext } from '../../../externalTypes'; import type { ImplicitlyInstallablePlugin } from '../../../ApolloServer'; import type { ApolloServerPluginLandingPageLocalDefaultOptions, @@ -10,11 +10,16 @@ import { getEmbeddedSandboxHTML, } from './getEmbeddedHTML.js'; +export type { + ApolloServerPluginLandingPageLocalDefaultOptions, + ApolloServerPluginLandingPageProductionDefaultOptions, +}; + export function ApolloServerPluginLandingPageLocalDefault< TContext extends BaseContext, >( options: ApolloServerPluginLandingPageLocalDefaultOptions = {}, -): ImplicitlyInstallablePlugin { +): ApolloServerPlugin { const { version, __internal_apolloStudioEnv__, ...rest } = { // we default to Sandbox unless embed is specified as false embed: true as const, @@ -31,7 +36,7 @@ export function ApolloServerPluginLandingPageProductionDefault< TContext extends BaseContext, >( options: ApolloServerPluginLandingPageProductionDefaultOptions = {}, -): ImplicitlyInstallablePlugin { +): ApolloServerPlugin { const { version, __internal_apolloStudioEnv__, ...rest } = options; return ApolloServerPluginLandingPageDefault(version, { isProd: true, diff --git a/packages/server/src/plugin/schemaReporting/index.ts b/packages/server/src/plugin/schemaReporting/index.ts index 96a157545c1..5ad21e37724 100644 --- a/packages/server/src/plugin/schemaReporting/index.ts +++ b/packages/server/src/plugin/schemaReporting/index.ts @@ -1,14 +1,14 @@ -import { createHash } from '@apollo/utils.createhash'; import os from 'os'; -import type { InternalApolloServerPlugin } from '../../internalPlugin'; +import { internalPlugin } from '../../internalPlugin.js'; import { v4 as uuidv4 } from 'uuid'; import { printSchema, validateSchema, buildSchema } from 'graphql'; import { SchemaReporter } from './schemaReporter.js'; import { schemaIsFederated } from '../schemaIsFederated.js'; import type { SchemaReport } from './generated/operations'; -import type { BaseContext } from '../../externalTypes'; +import type { ApolloServerPlugin, BaseContext } from '../../externalTypes'; import type { Fetcher } from '@apollo/utils.fetcher'; import { packageVersion } from '../../generated/packageVersion.js'; +import { computeCoreSchemaHash } from '../../utils/computeCoreSchemaHash.js'; export interface ApolloServerPluginSchemaReportingOptions { /** @@ -65,10 +65,10 @@ export function ApolloServerPluginSchemaReporting( endpointUrl, fetcher, }: ApolloServerPluginSchemaReportingOptions = Object.create(null), -): InternalApolloServerPlugin { +): ApolloServerPlugin { const bootId = uuidv4(); - return { + return internalPlugin({ __internal_plugin_id__() { return 'SchemaReporting'; }, @@ -196,9 +196,5 @@ export function ApolloServerPluginSchemaReporting( }, }; }, - }; -} - -export function computeCoreSchemaHash(schema: string): string { - return createHash('sha256').update(schema).digest('hex'); + }); } diff --git a/packages/server/src/plugin/usageReporting/plugin.ts b/packages/server/src/plugin/usageReporting/plugin.ts index 624ba5e4b12..ec84eba8d9f 100644 --- a/packages/server/src/plugin/usageReporting/plugin.ts +++ b/packages/server/src/plugin/usageReporting/plugin.ts @@ -12,6 +12,7 @@ import fetch from 'node-fetch'; import os from 'os'; import { gzip } from 'zlib'; import type { + ApolloServerPlugin, BaseContext, GraphQLRequestContext, GraphQLRequestContextDidResolveOperation, @@ -19,9 +20,8 @@ import type { GraphQLRequestListener, GraphQLServerListener, } from '../../externalTypes'; -import type { InternalApolloServerPlugin } from '../../internalPlugin'; +import { internalPlugin } from '../../internalPlugin.js'; import type { HeaderMap } from '../../runHttpQuery'; -import { computeCoreSchemaHash } from '../schemaReporting/index.js'; import { dateToProtoTimestamp, TraceTreeBuilder } from '../traceTreeBuilder.js'; import { defaultSendOperationsAsTrace } from './defaultSendOperationsAsTrace.js'; import { @@ -36,6 +36,7 @@ import type { import { OurReport } from './stats.js'; import { makeTraceDetails } from './traceDetails.js'; import { packageVersion } from '../../generated/packageVersion.js'; +import { computeCoreSchemaHash } from '../../utils/computeCoreSchemaHash.js'; const reportHeaderDefaults = { hostname: os.hostname(), @@ -65,7 +66,7 @@ export function ApolloServerPluginUsageReporting( options: ApolloServerPluginUsageReportingOptions = Object.create( null, ), -): InternalApolloServerPlugin { +): ApolloServerPlugin { // Note: We'd like to change the default to false in Apollo Server 4, so that // the default usage reporting experience doesn't include *anything* that // could potentially be PII (like error messages) --- just operations and @@ -84,7 +85,7 @@ export function ApolloServerPluginUsageReporting( let requestDidStartHandler: ( requestContext: GraphQLRequestContext, ) => GraphQLRequestListener; - return { + return internalPlugin({ __internal_plugin_id__() { return 'UsageReporting'; }, @@ -777,7 +778,7 @@ export function ApolloServerPluginUsageReporting( }, }; }, - }; + }); } export function makeHTTPRequestHeaders( diff --git a/packages/server/src/standalone/index.ts b/packages/server/src/standalone/index.ts index 45d06f95610..82bfb87adeb 100644 --- a/packages/server/src/standalone/index.ts +++ b/packages/server/src/standalone/index.ts @@ -23,7 +23,7 @@ export interface StandaloneServerContextFunctionArgument { res: ServerResponse; } -interface HTTPServerOptions { +export interface StartStandaloneServerOptions { context?: ContextFunction< [StandaloneServerContextFunctionArgument], TContext @@ -32,17 +32,19 @@ interface HTTPServerOptions { export async function startStandaloneServer( server: ApolloServer, - options?: HTTPServerOptions & { listen?: ListenOptions }, + options?: StartStandaloneServerOptions & { + listen?: ListenOptions; + }, ): Promise<{ url: string }>; export async function startStandaloneServer( server: ApolloServer, - options: WithRequired, 'context'> & { + options: WithRequired, 'context'> & { listen?: ListenOptions; }, ): Promise<{ url: string }>; export async function startStandaloneServer( server: ApolloServer, - options?: HTTPServerOptions & { listen?: ListenOptions }, + options?: StartStandaloneServerOptions & { listen?: ListenOptions }, ): Promise<{ url: string }> { const app: express.Express = express(); const httpServer: http.Server = http.createServer(app); diff --git a/packages/server/src/utils/computeCoreSchemaHash.ts b/packages/server/src/utils/computeCoreSchemaHash.ts new file mode 100644 index 00000000000..e4494ec51a1 --- /dev/null +++ b/packages/server/src/utils/computeCoreSchemaHash.ts @@ -0,0 +1,9 @@ +import { createHash } from '@apollo/utils.createhash'; + +// This hash function is used in both the schema reporting and usage reporting +// plugins. Making sure we use the same hash function hypothetically allows the +// two reporting features to work well together, though in practice nothing on +// the Studio side currently correlates this ID across both features. +export function computeCoreSchemaHash(schema: string): string { + return createHash('sha256').update(schema).digest('hex'); +}