diff --git a/packages/server/src/ApolloServer.ts b/packages/server/src/ApolloServer.ts index 4a9c8d97c9f..ef9d72c9f6c 100644 --- a/packages/server/src/ApolloServer.ts +++ b/packages/server/src/ApolloServer.ts @@ -74,6 +74,7 @@ import { UnreachableCaseError } from './utils/UnreachableCaseError.js'; import { computeCoreSchemaHash } from './utils/computeCoreSchemaHash.js'; import { isDefined } from './utils/isDefined.js'; import { SchemaManager } from './utils/schemaManager.js'; +import { GraphQLExecutor } from './externalTypes/requestPipeline.js'; const NoIntrospection: ValidationRule = (context: ValidationContext) => ({ Field(node) { @@ -184,6 +185,7 @@ export interface ApolloServerInternals { stringifyResult: ( value: FormattedExecutionResult, ) => string | Promise; + customExecutor?: GraphQLExecutor; } function defaultLogger(): Logger { @@ -330,6 +332,7 @@ export class ApolloServer { stopOnTerminationSignals: config.stopOnTerminationSignals, gatewayExecutor: null, // set by _start + customExecutor: config.customExecutor, csrfPreventionRequestHeaders: config.csrfPrevention === true || config.csrfPrevention === undefined diff --git a/packages/server/src/externalTypes/constructor.ts b/packages/server/src/externalTypes/constructor.ts index 6d6f354f655..d6cd6ff8777 100644 --- a/packages/server/src/externalTypes/constructor.ts +++ b/packages/server/src/externalTypes/constructor.ts @@ -20,6 +20,7 @@ import type { GatewayInterface } from '@apollo/server-gateway-interface'; import type { ApolloServerPlugin } from './plugins.js'; import type { BaseContext } from './index.js'; import type { GraphQLExperimentalIncrementalExecutionResults } from '../incrementalDeliveryPolyfill.js'; +import { GraphQLExecutor } from './requestPipeline.js'; export type DocumentStore = KeyValueCache; @@ -118,6 +119,8 @@ interface ApolloServerOptionsBase { // For testing only. __testing_incrementalExecutionResults?: GraphQLExperimentalIncrementalExecutionResults; + + customExecutor?: GraphQLExecutor; } export interface ApolloServerOptionsWithGateway diff --git a/packages/server/src/externalTypes/requestPipeline.ts b/packages/server/src/externalTypes/requestPipeline.ts index 75445cc7de2..7af284add00 100644 --- a/packages/server/src/externalTypes/requestPipeline.ts +++ b/packages/server/src/externalTypes/requestPipeline.ts @@ -121,3 +121,15 @@ export type GraphQLRequestContextDidEncounterSubsequentErrors< export type GraphQLRequestContextWillSendSubsequentPayload< TContext extends BaseContext, > = GraphQLRequestContextWillSendResponse; + +export type GraphQLExecutionResult = { + data?: Record | null; + errors?: ReadonlyArray; + extensions?: Record; +}; + +export type GraphQLExecutor< + TContext extends BaseContext = Record, +> = ( + requestContext: GraphQLRequestContextExecutionDidStart, +) => Promise; diff --git a/packages/server/src/plugin/cacheControl/index.ts b/packages/server/src/plugin/cacheControl/index.ts index 1972f42be35..4ec9615099f 100644 --- a/packages/server/src/plugin/cacheControl/index.ts +++ b/packages/server/src/plugin/cacheControl/index.ts @@ -48,6 +48,14 @@ export interface ApolloServerPluginCacheControlOptions { * delivery response, and the body contains no errors. */ calculateHttpHeaders?: boolean | 'if-cacheable'; + + /** + * Disables instrumenting all fields for dynamic cache control hints. This is + * a pretty big significant performant boost, especially the larger a response + * is. + */ + staticOnly?: boolean; + // For testing only. __testing__cacheHints?: Map; } @@ -125,10 +133,15 @@ export function ApolloServerPluginCacheControl( const defaultMaxAge: number = options.defaultMaxAge ?? 0; const calculateHttpHeaders = options.calculateHttpHeaders ?? true; + const staticOnly = options.staticOnly ?? false; const { __testing__cacheHints } = options; return { async executionDidStart() { + if (staticOnly) { + return {}; + } + // Did something set the overall cache policy before we've even // started? If so, consider that as an override and don't touch it. // Just put set up fake `info.cacheControl` objects and otherwise diff --git a/packages/server/src/plugin/subscriptionCallback/index.ts b/packages/server/src/plugin/subscriptionCallback/index.ts index a501ab07cef..04e316c31fa 100644 --- a/packages/server/src/plugin/subscriptionCallback/index.ts +++ b/packages/server/src/plugin/subscriptionCallback/index.ts @@ -199,6 +199,8 @@ function isAsyncIterable(value: any): value is AsyncIterable { return value && typeof value[Symbol.asyncIterator] === 'function'; } +type RetryResponse = Response | Error; + interface SubscriptionObject { cancelled: boolean; asyncIter: AsyncGenerator; @@ -265,7 +267,7 @@ class SubscriptionManager { `Sending \`${action}\` request to router` + maybeWithErrors, id, ); - return retry( + return retry( async (bail) => { response = fetch(url, { method: 'POST', diff --git a/packages/server/src/requestPipeline.ts b/packages/server/src/requestPipeline.ts index b9b4b5542dc..9eb55cff84a 100644 --- a/packages/server/src/requestPipeline.ts +++ b/packages/server/src/requestPipeline.ts @@ -539,6 +539,9 @@ export async function processGraphQLRequest( makeGatewayGraphQLRequestContext(requestContext, server, internals), ); return { singleResult: result }; + } else if (internals.customExecutor) { + const result = await internals.customExecutor(requestContext); + return { singleResult: result }; } else { const resultOrResults = await executeIncrementally({ schema: schemaDerivedData.schema,