Skip to content

Commit

Permalink
WIP of passing through plugins to pipeline and calling lifecycle me…
Browse files Browse the repository at this point in the history
…thods
  • Loading branch information
martijnwalraven committed Oct 8, 2018
1 parent f11914a commit a12673a
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 51 deletions.
1 change: 1 addition & 0 deletions packages/apollo-server-core/src/ApolloServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ export class ApolloServerBase {

return {
schema: this.schema,
plugins: this.plugins,
extensions: this.extensions,
context,
// Allow overrides from options. Be explicit about a couple of them to
Expand Down
2 changes: 2 additions & 0 deletions packages/apollo-server-core/src/graphqlOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { GraphQLExtension } from 'graphql-extensions';
import { CacheControlExtensionOptions } from 'apollo-cache-control';
import { KeyValueCache } from 'apollo-server-caching';
import { DataSource } from 'apollo-datasource';
import { ApolloServerPlugin } from 'apollo-server-plugin-base';

/*
* GraphQLServerOptions
Expand Down Expand Up @@ -41,6 +42,7 @@ export interface GraphQLServerOptions<
dataSources?: () => DataSources<TContext>;
cache?: KeyValueCache;
persistedQueries?: PersistedQueryOptions;
plugins?: ApolloServerPlugin[];
}

export type DataSources<TContext> = {
Expand Down
129 changes: 83 additions & 46 deletions packages/apollo-server-core/src/requestPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ import {
InvalidGraphQLRequestError,
ValidationRule,
} from './requestPipelineAPI';
import {
GraphQLRequestListener,
ApolloServerPlugin,
} from 'apollo-server-plugin-base';

export {
GraphQLRequest,
Expand All @@ -61,6 +65,8 @@ export interface GraphQLRequestPipelineConfig<TContext> {

formatError?: Function;
formatResponse?: Function;

plugins?: ApolloServerPlugin[];
}

export type DataSources<TContext> = {
Expand All @@ -81,67 +87,87 @@ export class GraphQLRequestPipeline<TContext> {
): Promise<GraphQLResponse> {
const config = this.config;

const requestListeners: GraphQLRequestListener<TContext>[] = [];
if (config.plugins) {
for (const plugin of config.plugins) {
if (!plugin.requestDidStart) continue;
const listener = plugin.requestDidStart(requestContext);
if (listener) {
requestListeners.push(listener);
}
}
}

const extensionStack = this.initializeExtensionStack();
(requestContext.context as any)._extensionStack = extensionStack;

this.initializeDataSources(requestContext);

await Promise.all(
requestListeners.map(
listener =>
listener.prepareRequest && listener.prepareRequest(requestContext),
),
);

const request = requestContext.request;

let { query, extensions } = request;

let persistedQueryHit = false;
let persistedQueryRegister = false;

if (extensions && extensions.persistedQuery) {
// It looks like we've received an Apollo Persisted Query. Check if we
// support them. In an ideal world, we always would, however since the
// middleware options are created every request, it does not make sense
// to create a default cache here and save a referrence to use across
// requests
if (
!this.config.persistedQueries ||
!this.config.persistedQueries.cache
) {
throw new PersistedQueryNotSupportedError();
} else if (extensions.persistedQuery.version !== 1) {
throw new InvalidGraphQLRequestError(
'Unsupported persisted query version',
);
}

const sha = extensions.persistedQuery.sha256Hash;

if (query === undefined) {
query =
(await this.config.persistedQueries.cache.get(`apq:${sha}`)) ||
undefined;
if (query) {
persistedQueryHit = true;
} else {
throw new PersistedQueryNotFoundError();
}
} else {
const hash = createHash('sha256');
const calculatedSha = hash.update(query).digest('hex');

if (sha !== calculatedSha) {
if (!query) {
if (extensions && extensions.persistedQuery) {
// It looks like we've received an Apollo Persisted Query. Check if we
// support them. In an ideal world, we always would, however since the
// middleware options are created every request, it does not make sense
// to create a default cache here and save a referrence to use across
// requests
if (
!this.config.persistedQueries ||
!this.config.persistedQueries.cache
) {
throw new PersistedQueryNotSupportedError();
} else if (extensions.persistedQuery.version !== 1) {
throw new InvalidGraphQLRequestError(
'provided sha does not match query',
'Unsupported persisted query version',
);
}
persistedQueryRegister = true;

// Do the store completely asynchronously
(async () => {
// We do not wait on the cache storage to complete
return (
this.config.persistedQueries &&
this.config.persistedQueries.cache.set(`apq:${sha}`, query)
);
})().catch(error => {
console.warn(error);
});

const sha = extensions.persistedQuery.sha256Hash;

if (query === undefined) {
query =
(await this.config.persistedQueries.cache.get(`apq:${sha}`)) ||
undefined;
if (query) {
persistedQueryHit = true;
} else {
throw new PersistedQueryNotFoundError();
}
} else {
const hash = createHash('sha256');
const calculatedSha = hash.update(query).digest('hex');

if (sha !== calculatedSha) {
throw new InvalidGraphQLRequestError(
'provided sha does not match query',
);
}
persistedQueryRegister = true;

// Do the store completely asynchronously
(async () => {
// We do not wait on the cache storage to complete
return (
this.config.persistedQueries &&
this.config.persistedQueries.cache.set(`apq:${sha}`, query)
);
})().catch(error => {
console.warn(error);
});
}
}
}

Expand Down Expand Up @@ -193,6 +219,17 @@ export class GraphQLRequestPipeline<TContext> {
this.willExecuteOperation(operation);
}

// FIXME: If we want to guarantee an operation has been set when invoking
// `executionDidStart`, we need to throw an error above and not leave this
// to `buildExecutionContext` in `graphql-js`.
requestContext.operation = operation as OperationDefinitionNode;

requestListeners.forEach(
listener =>
listener.executionDidStart &&
listener.executionDidStart(requestContext),
);

let response: GraphQLResponse;

try {
Expand Down
2 changes: 2 additions & 0 deletions packages/apollo-server-core/src/runHttpQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ export async function runHttpQuery(
formatResponse: options.formatResponse,

debug: options.debug,

plugins: options.plugins,
};

return processHTTPRequest(config, request);
Expand Down
10 changes: 5 additions & 5 deletions packages/apollo-server-plugin-base/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ export abstract class ApolloServerPlugin {
): GraphQLRequestListener<TContext> | void;
}

type WithRequired<T, K extends keyof T> = T & Required<Pick<T, K>>;
// type WithRequired<T, K extends keyof T> = T & Required<Pick<T, K>>;

export interface GraphQLRequestListener<TContext> {
prepareRequest?(requestContext: GraphQLRequestContext<TContext>): void;
executionDidStart?(
requestContext: WithRequired<GraphQLRequestContext<TContext>, 'operation'>,
): void;
prepareRequest?(
requestContext: GraphQLRequestContext<TContext>,
): Promise<void>;
executionDidStart?(requestContext: GraphQLRequestContext<TContext>): void;
}

0 comments on commit a12673a

Please sign in to comment.