Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

plugins: Declare types rather than use in-line types for life-cycle hooks. #3902

Merged
merged 3 commits into from
Mar 26, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions packages/apollo-gateway/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ import {
} from 'apollo-server-core';
import {
GraphQLExecutionResult,
GraphQLRequestContext,
Logger,
WithRequired,
GraphQLRequestContextExecutionDidStart,
} from 'apollo-server-types';
import { InMemoryLRUCache } from 'apollo-server-caching';
import {
Expand Down Expand Up @@ -148,11 +147,6 @@ export type Experimental_UpdateServiceDefinitions = (

type Await<T> = T extends Promise<infer U> ? U : T;

type RequestContext<TContext> = WithRequired<
GraphQLRequestContext<TContext>,
'document' | 'queryHash'
>;

// Local state to track whether particular UX-improving warning messages have
// already been emitted. This is particularly useful to prevent recurring
// warnings of the same type in, e.g. repeating timers, which don't provide
Expand Down Expand Up @@ -548,7 +542,7 @@ export class ApolloGateway implements GraphQLService {
// are unlikely to show up as GraphQLErrors. Do we need to use
// formatApolloErrors or something?
public executor = async <TContext>(
requestContext: RequestContext<TContext>,
requestContext: GraphQLRequestContextExecutionDidStart<TContext>,
): Promise<GraphQLExecutionResult> => {
const { request, document, queryHash } = requestContext;
const queryPlanStoreKey = queryHash + (request.operationName || '');
Expand Down Expand Up @@ -661,7 +655,7 @@ export class ApolloGateway implements GraphQLService {
};

protected validateIncomingRequest<TContext>(
requestContext: RequestContext<TContext>,
requestContext: GraphQLRequestContextExecutionDidStart<TContext>,
operationContext: OperationContext,
) {
// casting out of `readonly`
Expand Down
58 changes: 21 additions & 37 deletions packages/apollo-server-core/src/requestPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ import {
import {
ApolloServerPlugin,
GraphQLRequestListener,
GraphQLRequestContextExecutionDidStart,
GraphQLRequestContextResponseForOperation,
GraphQLRequestContextDidResolveOperation,
GraphQLRequestContextParsingDidStart,
GraphQLRequestContextValidationDidStart,
GraphQLRequestContextWillSendResponse,
GraphQLRequestContextDidEncounterErrors,
} from 'apollo-server-plugin-base';

import { Dispatcher } from './utils/dispatcher';
Expand Down Expand Up @@ -232,10 +239,7 @@ export async function processGraphQLRequest<TContext>(
if (!requestContext.document) {
const parsingDidEnd = await dispatcher.invokeDidStartHook(
'parsingDidStart',
requestContext as WithRequired<
typeof requestContext,
'metrics' | 'source'
>,
requestContext as GraphQLRequestContextParsingDidStart<TContext>,
);

try {
Expand All @@ -248,10 +252,7 @@ export async function processGraphQLRequest<TContext>(

const validationDidEnd = await dispatcher.invokeDidStartHook(
'validationDidStart',
requestContext as WithRequired<
typeof requestContext,
'document' | 'source' | 'metrics'
>,
requestContext as GraphQLRequestContextValidationDidStart<TContext>,
);

const validationErrors = validate(requestContext.document);
Expand Down Expand Up @@ -307,10 +308,7 @@ export async function processGraphQLRequest<TContext>(
try {
await dispatcher.invokeHookAsync(
'didResolveOperation',
requestContext as WithRequired<
typeof requestContext,
'document' | 'source' | 'operation' | 'operationName' | 'metrics'
>,
requestContext as GraphQLRequestContextDidResolveOperation<TContext>,
abernix marked this conversation as resolved.
Show resolved Hide resolved
);
} catch (err) {
// XXX: The HttpQueryError is special-cased here because we currently
Expand Down Expand Up @@ -354,25 +352,18 @@ export async function processGraphQLRequest<TContext>(

let response: GraphQLResponse | null = await dispatcher.invokeHooksUntilNonNull(
'responseForOperation',
requestContext as WithRequired<
typeof requestContext,
'document' | 'source' | 'operation' | 'operationName' | 'metrics'
>,
requestContext as GraphQLRequestContextResponseForOperation<TContext>,
);
if (response == null) {
const executionDidEnd = await dispatcher.invokeDidStartHook(
'executionDidStart',
requestContext as WithRequired<
typeof requestContext,
'document' | 'source' | 'operation' | 'operationName' | 'metrics'
>,
requestContext as GraphQLRequestContextExecutionDidStart<TContext>,
);

try {
const result = await execute(requestContext as WithRequired<
typeof requestContext,
'document' | 'operation' | 'operationName' | 'queryHash'
>);
const result = await execute(
requestContext as GraphQLRequestContextExecutionDidStart<TContext>,
);

if (result.errors) {
await didEncounterErrors(result.errors);
Expand Down Expand Up @@ -455,10 +446,7 @@ export async function processGraphQLRequest<TContext>(
}

async function execute(
requestContext: WithRequired<
GraphQLRequestContext<TContext>,
'document' | 'operationName' | 'operation' | 'queryHash'
>,
requestContext: GraphQLRequestContextExecutionDidStart<TContext>,
): Promise<GraphQLExecutionResult> {
const { request, document } = requestContext;

Expand All @@ -484,7 +472,9 @@ export async function processGraphQLRequest<TContext>(
// XXX Nothing guarantees that the only errors thrown or returned
// in result.errors are GraphQLErrors, even though other code
// (eg apollo-engine-reporting) assumes that.
return await config.executor(requestContext);
return await config.executor(
requestContext as GraphQLRequestContextExecutionDidStart<TContext>,
abernix marked this conversation as resolved.
Show resolved Hide resolved
);
} else {
return await graphql.execute(executionArgs);
}
Expand All @@ -509,10 +499,7 @@ export async function processGraphQLRequest<TContext>(
}).graphqlResponse;
await dispatcher.invokeHookAsync(
'willSendResponse',
requestContext as WithRequired<
typeof requestContext,
'metrics' | 'response'
>,
requestContext as GraphQLRequestContextWillSendResponse<TContext>,
);
return requestContext.response!;
}
Expand Down Expand Up @@ -543,10 +530,7 @@ export async function processGraphQLRequest<TContext>(

return await dispatcher.invokeHookAsync(
'didEncounterErrors',
requestContext as WithRequired<
typeof requestContext,
'metrics' | 'source' | 'errors'
>,
requestContext as GraphQLRequestContextDidEncounterErrors<TContext>,
);
}

Expand Down
8 changes: 2 additions & 6 deletions packages/apollo-server-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import {
ValueOrPromise,
GraphQLExecutor,
GraphQLExecutionResult,
WithRequired,
GraphQLRequestContext,
GraphQLRequestContextExecutionDidStart,
} from 'apollo-server-types';
import { ConnectionContext } from 'subscriptions-transport-ws';
// The types for `ws` use `export = WebSocket`, so we'll use the
Expand Down Expand Up @@ -97,10 +96,7 @@ export interface GraphQLService {
// Note: The `TContext` typing here is not conclusively behaving as we expect:
// https://github.com/apollographql/apollo-server/pull/3811#discussion_r387381605
executor<TContext>(
requestContext: WithRequired<
GraphQLRequestContext<TContext>,
'document' | 'queryHash' | 'operationName' | 'operation'
>,
requestContext: GraphQLRequestContextExecutionDidStart<TContext>,
): ValueOrPromise<GraphQLExecutionResult>;
}

Expand Down
58 changes: 30 additions & 28 deletions packages/apollo-server-plugin-base/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,37 @@ import {
GraphQLResponse,
ValueOrPromise,
WithRequired,
GraphQLRequestContextParsingDidStart,
GraphQLRequestContextValidationDidStart,
GraphQLRequestContextDidResolveOperation,
GraphQLRequestContextDidEncounterErrors,
GraphQLRequestContextResponseForOperation,
GraphQLRequestContextExecutionDidStart,
GraphQLRequestContextWillSendResponse,
} from 'apollo-server-types';

// We re-export all of these so plugin authors only need to depend on a single
// package. The overall concept of `apollo-server-types` and this package
// is that they not depend directly on "core", in order to avoid close
// coupling of plugin support with server versions. They are duplicated
// concepts right now where one package is intended to be for public plugin
// exposure, while the other (`-types`) is meant to be used internally.
// In the future, `apollo-server-types` and `apollo-server-plugin-base` will
// probably roll into the same "types" package, but that is not today!
export {
GraphQLServiceContext,
GraphQLRequestContext,
GraphQLRequest,
GraphQLResponse,
ValueOrPromise,
WithRequired,
GraphQLRequestContextParsingDidStart,
GraphQLRequestContextValidationDidStart,
GraphQLRequestContextDidResolveOperation,
GraphQLRequestContextDidEncounterErrors,
GraphQLRequestContextResponseForOperation,
GraphQLRequestContextExecutionDidStart,
GraphQLRequestContextWillSendResponse,
};

export interface ApolloServerPlugin<TContext extends Record<string, any> = Record<string, any>> {
Expand All @@ -24,50 +47,29 @@ export interface ApolloServerPlugin<TContext extends Record<string, any> = Recor

abernix marked this conversation as resolved.
Show resolved Hide resolved
export interface GraphQLRequestListener<TContext = Record<string, any>> {
parsingDidStart?(
requestContext: WithRequired<
GraphQLRequestContext<TContext>,
'metrics' | 'source'
>,
requestContext: GraphQLRequestContextParsingDidStart<TContext>,
): ((err?: Error) => void) | void;
validationDidStart?(
requestContext: WithRequired<
GraphQLRequestContext<TContext>,
'metrics' | 'source' | 'document'
>,
requestContext: GraphQLRequestContextValidationDidStart<TContext>,
): ((err?: ReadonlyArray<Error>) => void) | void;
didResolveOperation?(
requestContext: WithRequired<
GraphQLRequestContext<TContext>,
'metrics' | 'source' | 'document' | 'operationName' | 'operation'
>,
requestContext: GraphQLRequestContextDidResolveOperation<TContext>,
): ValueOrPromise<void>;
didEncounterErrors?(
requestContext: WithRequired<
GraphQLRequestContext<TContext>,
'metrics' | 'source' | 'errors'
>,
requestContext: GraphQLRequestContextDidEncounterErrors<TContext>,
): ValueOrPromise<void>;
// If this hook is defined, it is invoked immediately before GraphQL execution
// would take place. If its return value resolves to a non-null
// GraphQLResponse, that result is used instead of executing the query.
// Hooks from different plugins are invoked in series and the first non-null
// response is used.
responseForOperation?(
requestContext: WithRequired<
GraphQLRequestContext<TContext>,
'metrics' | 'source' | 'document' | 'operationName' | 'operation'
>,
requestContext: GraphQLRequestContextResponseForOperation<TContext>,
): ValueOrPromise<GraphQLResponse | null>;
executionDidStart?(
requestContext: WithRequired<
GraphQLRequestContext<TContext>,
'metrics' | 'source' | 'document' | 'operationName' | 'operation'
>,
requestContext: GraphQLRequestContextExecutionDidStart<TContext>,
): ((err?: Error) => void) | void;
willSendResponse?(
requestContext: WithRequired<
GraphQLRequestContext<TContext>,
'metrics' | 'response'
>,
requestContext: GraphQLRequestContextWillSendResponse<TContext>,
): ValueOrPromise<void>;
}
48 changes: 44 additions & 4 deletions packages/apollo-server-types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,7 @@ export type ValidationRule = (context: ValidationContext) => ASTVisitor;
export class InvalidGraphQLRequestError extends GraphQLError {}

export type GraphQLExecutor<TContext = Record<string, any>> = (
requestContext: WithRequired<
GraphQLRequestContext<TContext>,
'document' | 'operationName' | 'operation' | 'queryHash'
>,
requestContext: GraphQLRequestContextExecutionDidStart<TContext>,
) => ValueOrPromise<GraphQLExecutionResult>;

export type GraphQLExecutionResult = {
Expand All @@ -118,3 +115,46 @@ export type Logger = {
warn(message?: any): void;
error(message?: any): void;
}

export type GraphQLRequestContextParsingDidStart<TContext> =
WithRequired<GraphQLRequestContext<TContext>,
| 'metrics'
| 'source'
| 'queryHash'
>;
export type GraphQLRequestContextValidationDidStart<TContext> =
GraphQLRequestContextParsingDidStart<TContext> &
WithRequired<GraphQLRequestContext<TContext>,
| 'document'
>;
export type GraphQLRequestContextDidResolveOperation<TContext> =
GraphQLRequestContextValidationDidStart<TContext> &
WithRequired<GraphQLRequestContext<TContext>,
| 'operation'
| 'operationName'
>;
export type GraphQLRequestContextDidEncounterErrors<TContext> =
WithRequired<GraphQLRequestContext<TContext>,
| 'metrics'
| 'errors'
>;
export type GraphQLRequestContextResponseForOperation<TContext> =
WithRequired<GraphQLRequestContext<TContext>,
| 'metrics'
| 'source'
| 'document'
| 'operation'
| 'operationName'
>;
export type GraphQLRequestContextExecutionDidStart<TContext> =
GraphQLRequestContextParsingDidStart<TContext> &
WithRequired<GraphQLRequestContext<TContext>,
| 'document'
| 'operation'
| 'operationName'
>;
export type GraphQLRequestContextWillSendResponse<TContext> =
WithRequired<GraphQLRequestContext<TContext>,
| 'metrics'
| 'response'
>;