UNPKG

@benzene/http

Version:

Fast, minimal, agnostic GraphQL over HTTP

125 lines (121 loc) 4.39 kB
import { isExecutionResult, validateOperationName } from '@benzene/core'; export * from '@benzene/core'; import { GraphQLError } from 'graphql'; /** * Extract GraphQLParams from query string and body * @param request An object contains the body object and query object */ function getGraphQLParams({ query, body, }) { return { query: body?.query || query?.query, variables: body?.variables || (query?.variables && JSON.parse(query.variables)), operationName: body?.operationName || query?.operationName, extensions: body?.extensions || (query?.extensions && JSON.parse(query.extensions)), }; } /** * Parse the string body based on content-type according to the graphql-over-http spec * @param rawBody * @param oCtype * @see {@link https://graphql.org/learn/serving-over-http} */ function parseGraphQLBody(rawBody, oCtype = "") { const semiIndex = oCtype.indexOf(";"); const ctype = (semiIndex !== -1 ? oCtype.substring(0, semiIndex) : oCtype).trim(); switch (ctype) { case "application/graphql+json": try { return JSON.parse(rawBody); } catch (e) { throw new Error("POST body sent invalid JSON."); } case "application/json": try { return JSON.parse(rawBody); } catch (e) { throw new Error("POST body sent invalid JSON."); } case "application/graphql": return { query: rawBody }; default: // If no Content-Type header matches, parse nothing. return null; } } function createResponse(GQL, code, result) { return { payload: GQL.formatExecutionResult(result), status: code, headers: { "content-type": "application/json" }, }; } /** * Create a handler to handle incoming request * @param GQL A Benzene instance * @param options Handler options */ function makeHandler(GQL, // @ts-ignore options = {}) { /** * A function that handles incoming request * @param socket The incoming request * @param extra An extra field to store anything that needs to be accessed later */ return async function graphqlHTTP(request, extra) { let params = getGraphQLParams(request); if (options.onParams) { const onParamsResult = await options.onParams(params); if (onParamsResult) { if (isExecutionResult(onParamsResult)) { return createResponse(GQL, 200, onParamsResult); } params = onParamsResult; } } if (!params.query) { return createResponse(GQL, 400, { errors: [new GraphQLError("Must provide query string.")], }); } const cachedOrResult = GQL.compile(params.query, params.operationName); if (isExecutionResult(cachedOrResult)) { return createResponse(GQL, 400, cachedOrResult); } if (request.method !== "POST" && request.method !== "GET") { return createResponse(GQL, 405, { errors: [ new GraphQLError("GraphQL only supports GET and POST requests."), ], }); } const operationNameValidationErrors = validateOperationName(cachedOrResult.operation, params.operationName); if (operationNameValidationErrors.length > 0) { return createResponse(GQL, 400, { errors: operationNameValidationErrors, }); } if (request.method === "GET" && cachedOrResult.operation !== "query") { return createResponse(GQL, 405, { errors: [ new GraphQLError(`Can only perform a ${cachedOrResult.operation} operation from a POST request.`), ], }); } return createResponse(GQL, 200, await GQL.execute({ document: cachedOrResult.document, contextValue: GQL.contextFn ? await GQL.contextFn({ extra }) : undefined, variableValues: params.variables, operationName: params.operationName, compiled: cachedOrResult, })); }; } export { makeHandler, parseGraphQLBody };