apollo-server/packages/apollo-server-core/src/runQuery.ts

198 lines
6 KiB
TypeScript
Raw Normal View History

import {
GraphQLSchema,
GraphQLFieldResolver,
2017-01-04 18:03:11 -08:00
ExecutionResult,
DocumentNode,
parse,
print,
validate,
execute,
2017-08-09 16:57:17 +02:00
GraphQLError,
formatError,
2016-07-02 22:51:17 -07:00
specifiedRules,
2017-01-04 18:03:11 -08:00
ValidationContext,
} from 'graphql';
2017-10-23 19:01:02 -07:00
import { enableGraphQLExtensions, GraphQLExtension, GraphQLExtensionStack } from 'graphql-extensions';
import { TracingExtension } from 'apollo-tracing';
import { CacheControlExtension } from 'apollo-cache-control';
2017-08-09 16:57:17 +02:00
export interface GraphQLResponse {
data?: object;
errors?: Array<GraphQLError & object>;
extensions?: object;
}
export enum LogAction {
2017-01-23 13:10:42 +02:00
request, parse, validation, execute,
}
export enum LogStep {
2017-01-23 13:10:42 +02:00
start, end, status,
}
export interface LogMessage {
action: LogAction;
step: LogStep;
key?: string;
data?: Object;
}
export interface LogFunction {
(message: LogMessage);
}
export interface QueryOptions {
schema: GraphQLSchema;
2017-01-04 18:03:11 -08:00
query: string | DocumentNode;
rootValue?: any;
context?: any;
variables?: { [key: string]: any };
operationName?: string;
logFunction?: LogFunction;
2017-01-04 18:03:11 -08:00
validationRules?: Array<(context: ValidationContext) => any>;
fieldResolver?: GraphQLFieldResolver<any, any>;
// WARNING: these extra validation rules are only applied to queries
// submitted as string, not those submitted as Document!
formatError?: Function;
formatResponse?: Function;
debug?: boolean;
2017-08-09 16:57:17 +02:00
tracing?: boolean;
2017-10-23 19:01:02 -07:00
cacheControl?: boolean;
}
const resolvedPromise = Promise.resolve();
2017-08-09 16:57:17 +02:00
function runQuery(options: QueryOptions): Promise<GraphQLResponse> {
// Fiber-aware Promises run their .then callbacks in Fibers.
return resolvedPromise.then(() => doRunQuery(options));
}
2017-08-09 16:57:17 +02:00
function doRunQuery(options: QueryOptions): Promise<GraphQLResponse> {
2017-01-04 18:03:11 -08:00
let documentAST: DocumentNode;
const logFunction = options.logFunction || function(){ return null; };
const debugDefault = process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test';
const debug = typeof options.debug !== 'undefined' ? options.debug : debugDefault;
logFunction({action: LogAction.request, step: LogStep.start});
2017-08-09 16:57:17 +02:00
const context = options.context || {};
2017-10-23 19:01:02 -07:00
let extensions = [];
2017-08-09 16:57:17 +02:00
if (options.tracing) {
2017-10-23 19:01:02 -07:00
extensions.push(TracingExtension);
}
if (options.cacheControl) {
extensions.push(CacheControlExtension);
}
const extensionStack = extensions.length > 0 && new GraphQLExtensionStack(extensions);
if (extensionStack) {
context._extensionStack = extensionStack;
enableGraphQLExtensions(options.schema);
extensionStack.requestDidStart();
2017-08-09 16:57:17 +02:00
}
2016-07-02 22:51:17 -07:00
function format(errors: Array<Error>): Array<Error> {
2016-10-17 13:55:10 -03:00
return errors.map((error) => {
if (options.formatError) {
try {
return options.formatError(error);
} catch (err) {
console.error('Error in formatError function:', err);
2016-10-17 16:17:23 -03:00
const newError = new Error('Internal server error');
return formatError(newError);
2016-10-17 13:55:10 -03:00
}
} else {
return formatError(error);
}
}) as Array<Error>;
2016-07-02 22:51:17 -07:00
}
2016-06-24 17:16:33 -04:00
function printStackTrace(error: Error) {
console.error(error.stack);
}
const qry = typeof options.query === 'string' ? options.query : print(options.query);
logFunction({action: LogAction.request, step: LogStep.status, key: 'query', data: qry});
logFunction({action: LogAction.request, step: LogStep.status, key: 'variables', data: options.variables});
logFunction({action: LogAction.request, step: LogStep.status, key: 'operationName', data: options.operationName});
// if query is already an AST, don't parse or validate
// XXX: This refers the operations-store flow.
if (typeof options.query === 'string') {
try {
logFunction({action: LogAction.parse, step: LogStep.start});
documentAST = parse(options.query as string);
logFunction({action: LogAction.parse, step: LogStep.end});
} catch (syntaxError) {
logFunction({action: LogAction.parse, step: LogStep.end});
2016-07-02 22:51:17 -07:00
return Promise.resolve({ errors: format([syntaxError]) });
}
} else {
2017-01-04 18:03:11 -08:00
documentAST = options.query as DocumentNode;
}
let rules = specifiedRules;
if (options.validationRules) {
rules = rules.concat(options.validationRules);
}
logFunction({action: LogAction.validation, step: LogStep.start});
const validationErrors = validate(options.schema, documentAST, rules);
logFunction({action: LogAction.validation, step: LogStep.end});
if (validationErrors.length) {
return Promise.resolve({ errors: format(validationErrors) });
}
2017-10-23 19:01:02 -07:00
if (extensionStack) {
extensionStack.executionDidStart();
2017-08-09 16:57:17 +02:00
}
2016-06-24 16:57:52 -04:00
try {
logFunction({action: LogAction.execute, step: LogStep.start});
2016-06-24 16:57:52 -04:00
return execute(
2016-06-24 17:16:33 -04:00
options.schema,
2016-06-24 16:57:52 -04:00
documentAST,
2016-06-24 17:16:33 -04:00
options.rootValue,
2017-08-09 16:57:17 +02:00
context,
2016-06-24 17:16:33 -04:00
options.variables,
2017-01-23 13:10:42 +02:00
options.operationName,
options.fieldResolver,
2017-08-09 16:57:17 +02:00
).then(result => {
logFunction({action: LogAction.execute, step: LogStep.end});
logFunction({action: LogAction.request, step: LogStep.end});
2017-08-09 16:57:17 +02:00
let response: GraphQLResponse = {
data: result.data,
};
2017-08-09 16:57:17 +02:00
if (result.errors) {
response.errors = format(result.errors);
if (debug) {
2017-08-09 16:57:17 +02:00
result.errors.map(printStackTrace);
}
}
2017-08-09 16:57:17 +02:00
2017-10-23 19:01:02 -07:00
if (extensionStack) {
extensionStack.executionDidEnd();
extensionStack.requestDidEnd();
response.extensions = extensionStack.format();
2017-08-09 16:57:17 +02:00
}
if (options.formatResponse) {
2016-08-05 11:42:46 -07:00
response = options.formatResponse(response, options);
}
2017-08-09 16:57:17 +02:00
return response;
});
2016-06-24 16:57:52 -04:00
} catch (executionError) {
logFunction({action: LogAction.execute, step: LogStep.end});
logFunction({action: LogAction.request, step: LogStep.end});
2016-07-02 22:51:17 -07:00
return Promise.resolve({ errors: format([executionError]) });
2016-06-24 16:57:52 -04:00
}
}
export { runQuery };