2016-06-10 20:48:21 -04:00
|
|
|
import {
|
|
|
|
GraphQLSchema,
|
|
|
|
GraphQLResult,
|
|
|
|
Document,
|
|
|
|
parse,
|
2016-08-05 11:14:56 -07:00
|
|
|
print,
|
2016-06-10 20:48:21 -04:00
|
|
|
validate,
|
|
|
|
execute,
|
2016-06-27 21:58:22 -04:00
|
|
|
formatError,
|
2016-07-02 22:51:17 -07:00
|
|
|
specifiedRules,
|
|
|
|
ValidationRule,
|
2016-06-10 20:48:21 -04:00
|
|
|
} from 'graphql';
|
2016-06-10 17:05:39 -07:00
|
|
|
|
2016-06-10 20:48:21 -04:00
|
|
|
export interface GqlResponse {
|
|
|
|
data?: Object;
|
|
|
|
errors?: Array<string>;
|
2016-06-10 17:05:39 -07:00
|
|
|
}
|
|
|
|
|
2016-09-10 17:28:38 -05:00
|
|
|
export enum LogAction {
|
|
|
|
request, parse, validation, execute
|
|
|
|
}
|
|
|
|
|
|
|
|
export enum LogStep {
|
|
|
|
start, end, status
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface LogMessage {
|
|
|
|
action: LogAction;
|
|
|
|
step: LogStep;
|
|
|
|
key?: string;
|
|
|
|
data?: Object;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface LogFunction {
|
|
|
|
(message: LogMessage);
|
|
|
|
}
|
|
|
|
|
2016-06-18 10:19:51 -07:00
|
|
|
export interface QueryOptions {
|
|
|
|
schema: GraphQLSchema;
|
|
|
|
query: string | Document;
|
|
|
|
rootValue?: any;
|
|
|
|
context?: any;
|
|
|
|
variables?: { [key: string]: any };
|
|
|
|
operationName?: string;
|
2016-09-10 17:28:38 -05:00
|
|
|
logFunction?: LogFunction;
|
2016-07-02 22:51:17 -07:00
|
|
|
validationRules?: Array<ValidationRule>;
|
2016-07-02 22:59:08 -07:00
|
|
|
// WARNING: these extra validation rules are only applied to queries
|
|
|
|
// submitted as string, not those submitted as Document!
|
|
|
|
|
2016-06-27 21:58:22 -04:00
|
|
|
formatError?: Function;
|
|
|
|
formatResponse?: Function;
|
2016-09-12 15:02:41 -07:00
|
|
|
debug?: boolean;
|
2016-06-18 10:19:51 -07:00
|
|
|
}
|
|
|
|
|
2016-08-14 03:19:31 -04:00
|
|
|
const resolvedPromise = Promise.resolve();
|
|
|
|
|
2016-06-18 10:19:51 -07:00
|
|
|
function runQuery(options: QueryOptions): Promise<GraphQLResult> {
|
2016-08-14 03:19:31 -04:00
|
|
|
// Fiber-aware Promises run their .then callbacks in Fibers.
|
|
|
|
return resolvedPromise.then(() => doRunQuery(options));
|
|
|
|
}
|
|
|
|
|
|
|
|
function doRunQuery(options: QueryOptions): Promise<GraphQLResult> {
|
2016-06-10 17:05:39 -07:00
|
|
|
let documentAST: Document;
|
|
|
|
|
2016-08-05 11:14:56 -07:00
|
|
|
const logFunction = options.logFunction || function(){ return null; };
|
2016-09-12 15:02:41 -07:00
|
|
|
const debugDefault = process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test';
|
|
|
|
const debug = typeof options.debug !== 'undefined' ? options.debug : debugDefault;
|
2016-08-05 11:14:56 -07:00
|
|
|
|
2016-09-10 17:28:38 -05:00
|
|
|
logFunction({action: LogAction.request, step: LogStep.start});
|
2016-08-05 11:14:56 -07: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);
|
|
|
|
return formatError(error);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return formatError(error);
|
|
|
|
}
|
|
|
|
}) as Array<Error>;
|
2016-07-02 22:51:17 -07:00
|
|
|
}
|
2016-06-24 17:16:33 -04:00
|
|
|
|
2016-09-12 15:02:41 -07:00
|
|
|
function printStackTrace(error: Error) {
|
|
|
|
console.error(error.stack);
|
|
|
|
}
|
|
|
|
|
2016-09-10 17:28:38 -05:00
|
|
|
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});
|
2016-08-05 11:14:56 -07:00
|
|
|
|
2016-06-10 17:05:39 -07:00
|
|
|
// if query is already an AST, don't parse or validate
|
2016-06-18 10:19:51 -07:00
|
|
|
if (typeof options.query === 'string') {
|
2016-06-10 17:05:39 -07:00
|
|
|
try {
|
2016-06-27 19:19:05 -04:00
|
|
|
// TODO: time this with log function
|
2016-09-10 17:28:38 -05:00
|
|
|
logFunction({action: LogAction.parse, step: LogStep.start});
|
2016-06-18 10:19:51 -07:00
|
|
|
documentAST = parse(options.query as string);
|
2016-09-10 17:28:38 -05:00
|
|
|
logFunction({action: LogAction.parse, step: LogStep.end});
|
2016-06-10 17:05:39 -07:00
|
|
|
} catch (syntaxError) {
|
2016-09-10 17:28:38 -05:00
|
|
|
logFunction({action: LogAction.parse, step: LogStep.end});
|
2016-07-02 22:51:17 -07:00
|
|
|
return Promise.resolve({ errors: format([syntaxError]) });
|
2016-06-10 17:05:39 -07:00
|
|
|
}
|
|
|
|
|
2016-06-27 19:19:05 -04:00
|
|
|
// TODO: time this with log function
|
2016-06-27 21:58:22 -04:00
|
|
|
|
2016-07-02 22:51:17 -07:00
|
|
|
let rules = specifiedRules;
|
|
|
|
if (options.validationRules) {
|
|
|
|
rules = rules.concat(options.validationRules);
|
|
|
|
}
|
2016-09-10 17:28:38 -05:00
|
|
|
logFunction({action: LogAction.validation, step: LogStep.start});
|
2016-07-02 22:51:17 -07:00
|
|
|
const validationErrors = validate(options.schema, documentAST, rules);
|
2016-09-10 17:28:38 -05:00
|
|
|
logFunction({action: LogAction.validation, step: LogStep.end});
|
2016-06-10 17:05:39 -07:00
|
|
|
if (validationErrors.length) {
|
2016-07-02 22:51:17 -07:00
|
|
|
return Promise.resolve({ errors: format(validationErrors) });
|
2016-06-10 17:05:39 -07:00
|
|
|
}
|
|
|
|
} else {
|
2016-06-18 10:19:51 -07:00
|
|
|
documentAST = options.query as Document;
|
2016-06-10 17:05:39 -07:00
|
|
|
}
|
|
|
|
|
2016-06-24 16:57:52 -04:00
|
|
|
try {
|
2016-09-10 17:28:38 -05:00
|
|
|
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,
|
|
|
|
options.context,
|
|
|
|
options.variables,
|
|
|
|
options.operationName
|
2016-06-27 21:58:22 -04:00
|
|
|
).then(gqlResponse => {
|
2016-09-10 17:28:38 -05:00
|
|
|
logFunction({action: LogAction.execute, step: LogStep.end});
|
|
|
|
logFunction({action: LogAction.request, step: LogStep.end});
|
2016-06-27 21:58:22 -04:00
|
|
|
let response = {
|
|
|
|
data: gqlResponse.data,
|
|
|
|
};
|
|
|
|
if (gqlResponse.errors) {
|
2016-07-02 22:51:17 -07:00
|
|
|
response['errors'] = format(gqlResponse.errors);
|
2016-09-12 15:02:41 -07:00
|
|
|
if (debug) {
|
|
|
|
gqlResponse.errors.map(printStackTrace);
|
|
|
|
}
|
2016-06-27 21:58:22 -04:00
|
|
|
}
|
|
|
|
if (options.formatResponse) {
|
2016-08-05 11:42:46 -07:00
|
|
|
response = options.formatResponse(response, options);
|
2016-06-27 21:58:22 -04:00
|
|
|
}
|
|
|
|
return response;
|
|
|
|
});
|
2016-06-24 16:57:52 -04:00
|
|
|
} catch (executionError) {
|
2016-09-10 17:28:38 -05:00
|
|
|
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
|
|
|
}
|
2016-06-10 17:05:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
export { runQuery };
|