UNPKG

graphql-helix

Version:

A highly evolved GraphQL HTTP Server 🧬

289 lines (288 loc) • 13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.processRequest = exports.validateDocument = void 0; const graphql_1 = require("graphql"); const index_1 = require("./util/index"); const errors_1 = require("./errors"); const get_ranked_response_protocols_1 = require("./util/get-ranked-response-protocols"); const parseQuery = (query, parse) => { if (typeof query !== "string" && query.kind === "Document") { return query; } try { const parseResult = parse(query); if (parseResult instanceof Promise) { return parseResult.catch((syntaxError) => { throw new errors_1.HttpError(400, "GraphQL syntax error.", { graphqlErrors: [syntaxError], }); }); } return parseResult; } catch (syntaxError) { throw new errors_1.HttpError(400, "GraphQL syntax error.", { graphqlErrors: [syntaxError], }); } }; const validateDocument = (schema, document, validate, validationRules) => { const validationErrors = validate(schema, document, validationRules); if (validationErrors.length) { throw new errors_1.HttpError(400, "GraphQL validation error.", { graphqlErrors: validationErrors, }); } }; exports.validateDocument = validateDocument; const getExecutableOperation = (document, operationName) => { const operation = (0, graphql_1.getOperationAST)(document, operationName); if (!operation) { throw new errors_1.HttpError(400, "Could not determine what operation to execute."); } return operation; }; // If clients do not accept application/graphql+json use application/json - otherwise respect the order in the accept header const getSingleResponseContentType = (protocols) => { if (protocols["application/graphql+json"] === -1) { return "application/json"; } return protocols["application/graphql+json"] > protocols["application/json"] ? "application/graphql+json" : "application/json"; }; const getHeader = (request, headerName) => typeof request.headers.get === "function" ? request.headers.get(headerName) : request.headers[headerName]; const processRequest = async (options) => { const { contextFactory, execute = graphql_1.execute, formatPayload = ({ payload }) => payload, operationName, extensions, parse = graphql_1.parse, query, request, rootValueFactory, schema, subscribe = graphql_1.subscribe, validate = graphql_1.validate, validationRules, variables, allowedSubscriptionHttpMethods = ["GET", "POST"], } = options; let context; let rootValue; let document; let operation; const result = await (async () => { const accept = getHeader(request, "accept"); const contentType = getHeader(request, "contentType"); const rankedProtocols = (0, get_ranked_response_protocols_1.getRankedResponseProtocols)(accept, contentType); const isEventStreamAccepted = rankedProtocols["text/event-stream"] !== -1; const defaultSingleResponseHeaders = [ { name: "Content-Type", value: getSingleResponseContentType(rankedProtocols), }, ]; try { if (!(0, index_1.isHttpMethod)("GET", request.method) && !(0, index_1.isHttpMethod)("POST", request.method)) { throw new errors_1.HttpError(405, "GraphQL only supports GET and POST requests.", { headers: [...defaultSingleResponseHeaders, { name: "Allow", value: "GET, POST" }], }); } if (query == null) { throw new errors_1.HttpError(400, "Must provide query string.", { headers: defaultSingleResponseHeaders }); } document = await parseQuery(query, parse); (0, exports.validateDocument)(schema, document, validate, validationRules); operation = getExecutableOperation(document, operationName); if (operation.operation === "mutation" && (0, index_1.isHttpMethod)("GET", request.method)) { throw new errors_1.HttpError(405, "Can only perform a mutation operation from a POST request.", { headers: [...defaultSingleResponseHeaders, { name: "Allow", value: "POST" }], }); } let variableValues; try { if (variables) { variableValues = typeof variables === "string" ? JSON.parse(variables) : variables; } } catch (_error) { throw new errors_1.HttpError(400, "Variables are invalid JSON.", { headers: defaultSingleResponseHeaders, }); } try { const executionContext = { request, document, operation, operationName, extensions, variables: variableValues, }; context = contextFactory ? await contextFactory(executionContext) : {}; rootValue = rootValueFactory ? await rootValueFactory(executionContext) : {}; if (operation.operation === "subscription") { if (!allowedSubscriptionHttpMethods.some((method) => (0, index_1.isHttpMethod)(method, request.method))) { throw new errors_1.HttpError(405, `Can only perform subscription operation from a ${allowedSubscriptionHttpMethods.join(" or ")} request.`, { headers: [ ...defaultSingleResponseHeaders, { name: "Allow", value: allowedSubscriptionHttpMethods.join(", "), }, ], }); } const result = await subscribe({ schema, document, rootValue, contextValue: context, variableValues, operationName, }); // If errors are encountered while subscribing to the operation, an execution result // instead of an AsyncIterable. if ((0, index_1.isAsyncIterable)(result)) { return { type: "PUSH", subscribe: async (onResult) => { try { for await (const payload of result) { onResult(formatPayload({ payload, context, rootValue, document, operation, })); } } catch (error) { const payload = { errors: error.graphqlErrors || [ new graphql_1.GraphQLError(error.message), ], }; onResult(formatPayload({ payload, context, rootValue, document, operation, })); (0, index_1.stopAsyncIteration)(result); } }, unsubscribe: () => { (0, index_1.stopAsyncIteration)(result); }, }; } return { type: "PUSH", subscribe: async (onResult) => { onResult(formatPayload({ payload: result, context, rootValue, document, operation, })); }, unsubscribe: () => undefined, }; } else { const result = await execute({ schema, document, rootValue, contextValue: context, variableValues, operationName, }); // Operations that use @defer, @stream and @live will return an `AsyncIterable` instead of an // execution result. if ((0, index_1.isAsyncIterable)(result)) { return { type: (0, index_1.isHttpMethod)("GET", request.method) ? "PUSH" : "MULTIPART_RESPONSE", subscribe: async (onResult) => { for await (const payload of result) { onResult(formatPayload({ payload, context, rootValue, document, operation, })); } }, unsubscribe: () => { (0, index_1.stopAsyncIteration)(result); }, }; } else { return { type: "RESPONSE", status: 200, headers: defaultSingleResponseHeaders, payload: formatPayload({ payload: result, context, rootValue, document, operation, }), }; } } } catch (executionError) { if (executionError instanceof graphql_1.GraphQLError) { throw new errors_1.HttpError(200, "GraphQLError encountered white executed GraphQL request.", { graphqlErrors: [executionError], headers: defaultSingleResponseHeaders, }); } else if (executionError instanceof errors_1.HttpError) { throw executionError; } else { throw new errors_1.HttpError(500, "Unexpected error encountered while executing GraphQL request.", { graphqlErrors: [new graphql_1.GraphQLError(executionError.message)], headers: defaultSingleResponseHeaders, }); } } } catch (error) { const payload = { errors: error.graphqlErrors || [new graphql_1.GraphQLError(error.message)], }; if (isEventStreamAccepted) { return { type: "PUSH", subscribe: async (onResult) => { onResult(formatPayload({ payload, context, rootValue, document, operation, })); }, unsubscribe: () => undefined, }; } else { return { type: "RESPONSE", status: error.status || 500, headers: error.headers || [], payload: formatPayload({ payload, context, rootValue, document, operation, }), }; } } })(); return { ...result, context, rootValue, document, operation, }; }; exports.processRequest = processRequest;