@benzene/http
Version:
Fast, minimal, agnostic GraphQL over HTTP
135 lines (129 loc) • 4.68 kB
JavaScript
;
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]; }
});
});