UNPKG

@benzene/http

Version:

Fast, minimal, agnostic GraphQL over HTTP

135 lines (129 loc) 4.68 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var core = require('@benzene/core'); var graphql = require('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 (core.isExecutionResult(onParamsResult)) { return createResponse(GQL, 200, onParamsResult); } params = onParamsResult; } } if (!params.query) { return createResponse(GQL, 400, { errors: [new graphql.GraphQLError("Must provide query string.")], }); } const cachedOrResult = GQL.compile(params.query, params.operationName); if (core.isExecutionResult(cachedOrResult)) { return createResponse(GQL, 400, cachedOrResult); } if (request.method !== "POST" && request.method !== "GET") { return createResponse(GQL, 405, { errors: [ new graphql.GraphQLError("GraphQL only supports GET and POST requests."), ], }); } const operationNameValidationErrors = core.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 graphql.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, })); }; } exports.makeHandler = makeHandler; exports.parseGraphQLBody = parseGraphQLBody; Object.keys(core).forEach(function (k) { if (k !== 'default' && !exports.hasOwnProperty(k)) Object.defineProperty(exports, k, { enumerable: true, get: function () { return core[k]; } }); });