diff --git a/packages/apollo-server-lambda/src/ApolloServer.ts b/packages/apollo-server-lambda/src/ApolloServer.ts index 0c91836de2f..31d958a3506 100644 --- a/packages/apollo-server-lambda/src/ApolloServer.ts +++ b/packages/apollo-server-lambda/src/ApolloServer.ts @@ -101,7 +101,7 @@ export class ApolloServer extends ApolloServerBase { return ( event: APIGatewayProxyEvent, context: LambdaContext, - callback: APIGatewayProxyCallback, + callback?: APIGatewayProxyCallback, ) => { // We re-load the headers into a Fetch API-compatible `Headers` // interface within `graphqlLambda`, but we still need to respect the @@ -150,14 +150,20 @@ export class ApolloServer extends ApolloServerBase { }, {}); if (event.httpMethod === 'OPTIONS') { - context.callbackWaitsForEmptyEventLoop = false; - return callback(null, { + const result = { body: '', statusCode: 204, headers: { ...requestCorsHeadersObject, }, - }); + }; + + if (callback) { + context.callbackWaitsForEmptyEventLoop = false; + return callback(null, result); + } else { + return Promise.resolve(result); + } } if (this.playgroundOptions && event.httpMethod === 'GET') { @@ -173,41 +179,51 @@ export class ApolloServer extends ApolloServerBase { ...this.playgroundOptions, }; - return callback(null, { + const result = { body: renderPlaygroundPage(playgroundRenderPageOptions), statusCode: 200, headers: { 'Content-Type': 'text/html', ...requestCorsHeadersObject, }, - }); + }; + + if (callback) { + return callback(null, result); + } else { + return Promise.resolve(result); + } } } - const callbackFilter: APIGatewayProxyCallback = (error, result) => { - callback( - error, - result && { - ...result, - headers: { - ...result.headers, - ...requestCorsHeadersObject, - }, - }, - ); - }; + if (callback) { + // Maintain existing behavior + context.callbackWaitsForEmptyEventLoop = false; + } - graphqlLambda(async () => { - // In a world where this `createHandler` was async, we might avoid this - // but since we don't want to introduce a breaking change to this API - // (by switching it to `async`), we'll leverage the - // `GraphQLServerOptions`, which are dynamically built on each request, - // to `await` the `promiseWillStart` which we kicked off at the top of - // this method to ensure that it runs to completion (which is part of - // its contract) prior to processing the request. - await promiseWillStart; - return this.createGraphQLServerOptions(event, context); - })(event, context, callbackFilter); + const resultPromise = promiseWillStart.then(() => { + return this.createGraphQLServerOptions(event, context).then(options => { + return graphqlLambda(options)(event, context).then(result => { + return result && { + ...result, + headers: { + ...result.headers, + ...requestCorsHeadersObject, + }, + }; + }); + }); + }); + + if (callback) { + resultPromise.then(result => { + callback(null, result); + }).catch(error => { + callback(error); + }) + } else { + return resultPromise; + } }; } } diff --git a/packages/apollo-server-lambda/src/lambdaApollo.ts b/packages/apollo-server-lambda/src/lambdaApollo.ts index 2ce5be47853..77bdde11a13 100644 --- a/packages/apollo-server-lambda/src/lambdaApollo.ts +++ b/packages/apollo-server-lambda/src/lambdaApollo.ts @@ -4,17 +4,17 @@ import { HttpQueryError, runHttpQuery, } from 'apollo-server-core'; -import { Headers, ValueOrPromise } from 'apollo-server-env'; +import { Headers } from 'apollo-server-env'; -export interface LambdaGraphQLOptionsFunction { - (event: lambda.APIGatewayProxyEvent, context: lambda.Context): ValueOrPromise< - GraphQLOptions +export interface APIGatewayProxyAsyncHandler { + (event: lambda.APIGatewayProxyEvent, context: lambda.Context): Promise< + lambda.APIGatewayProxyResult >; } export function graphqlLambda( - options: GraphQLOptions | LambdaGraphQLOptionsFunction, -): lambda.APIGatewayProxyHandler { + options: GraphQLOptions, +): APIGatewayProxyAsyncHandler { if (!options) { throw new Error('Apollo Server requires options.'); } @@ -25,48 +25,51 @@ export function graphqlLambda( ); } - const graphqlHandler: lambda.APIGatewayProxyHandler = ( + const graphqlHandler: APIGatewayProxyAsyncHandler = async ( event, - context, - callback, - ): void => { - context.callbackWaitsForEmptyEventLoop = false; - + context + ) => { if (event.httpMethod === 'POST' && !event.body) { - return callback(null, { + return { body: 'POST body missing.', statusCode: 500, - }); + }; } - runHttpQuery([event, context], { - method: event.httpMethod, - options: options, - query: - event.httpMethod === 'POST' && event.body - ? JSON.parse(event.body) - : event.queryStringParameters, - request: { - url: event.path, + + try { + const { + graphqlResponse, + responseInit + } = await runHttpQuery([event, context], { method: event.httpMethod, - headers: new Headers(event.headers), - }, - }).then( - ({ graphqlResponse, responseInit }) => { - callback(null, { - body: graphqlResponse, - statusCode: 200, - headers: responseInit.headers, - }); - }, - (error: HttpQueryError) => { - if ('HttpQueryError' !== error.name) return callback(error); - callback(null, { + options: options, + query: + event.httpMethod === 'POST' && event.body + ? JSON.parse(event.body) + : event.queryStringParameters, + request: { + url: event.path, + method: event.httpMethod, + headers: new Headers(event.headers), + }, + }); + + return { + body: graphqlResponse, + statusCode: 200, + headers: responseInit.headers, + }; + } catch (error) { + if (error instanceof HttpQueryError) { + return { body: error.message, statusCode: error.statusCode, headers: error.headers, - }); - }, - ); + }; + } + + throw error; + } }; return graphqlHandler;