grafast
Version:
Cutting edge GraphQL planning and execution engine
155 lines • 6.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.grafast = grafast;
exports.grafastSync = grafastSync;
const tslib_1 = require("tslib");
const lru_1 = tslib_1.__importDefault(require("@graphile/lru"));
const graphql = tslib_1.__importStar(require("graphql"));
const constants_js_1 = require("./constants.js");
const error_js_1 = require("./error.js");
const execute_js_1 = require("./execute.js");
const index_js_1 = require("./index.js");
const middleware_js_1 = require("./middleware.js");
const utils_js_1 = require("./utils.js");
const { GraphQLError, parse, Source, validate, validateSchema } = graphql;
/** Rough average size per query */
const CACHE_MULTIPLIER = 100000;
const MEGABYTE = 1024 * 1024;
const queryCacheMaxSizeInBytes = 50 * MEGABYTE;
const defaultQueryCacheMaxSize = Math.max(2, Math.ceil(queryCacheMaxSizeInBytes / CACHE_MULTIPLIER));
// If we can use crypto to create a hash, great. Otherwise just use the string.
let calculateQueryHash;
try {
let lastString;
let lastHash;
// eslint-disable-next-line @typescript-eslint/no-require-imports
const createHash = require("crypto").createHash;
if (typeof createHash !== "function") {
throw new Error("Failed to load createHash");
}
calculateQueryHash = (queryString) => {
if (queryString !== lastString) {
lastString = queryString;
lastHash = createHash("sha1").update(queryString).digest("base64");
}
return lastHash;
};
}
catch {
calculateQueryHash = (str) => str;
}
const parseAndValidate = (gqlSchema, stringOrSource) => {
let queryCache = gqlSchema.extensions.grafast?.[constants_js_1.$$queryCache];
if (!queryCache) {
const cacheSize = gqlSchema.extensions.grafast?.queryCacheMaxLength ??
defaultQueryCacheMaxSize;
queryCache = new lru_1.default({ maxLength: cacheSize });
if (!gqlSchema.extensions.grafast) {
gqlSchema.extensions.grafast = Object.create(null);
}
gqlSchema.extensions.grafast[constants_js_1.$$queryCache] = queryCache;
}
// Only cache queries that are less than 100kB, we don't want DOS attacks
// attempting to exhaust our memory.
const hash = calculateQueryHash(typeof stringOrSource === "string" ? stringOrSource : stringOrSource.body);
const result = queryCache.get(hash);
if (result !== undefined) {
return result;
}
else {
const source = typeof stringOrSource === "string"
? new Source(stringOrSource, "GraphQL Http Request")
: stringOrSource;
let queryDocumentAst;
// Catch an errors while parsing so that we can set the `statusCode` to
// 400. Otherwise we don’t need to parse this way.
try {
queryDocumentAst = parse(source);
// Validate our GraphQL query using given rules.
const validationErrors = validate(gqlSchema, queryDocumentAst);
const cacheResult = validationErrors.length > 0 ? validationErrors : queryDocumentAst;
queryCache.set(hash, cacheResult);
return cacheResult;
}
catch (error) {
const cacheResult = [
error instanceof GraphQLError
? error
: new GraphQLError("Validation error occurred", undefined, undefined, undefined, undefined, error),
];
queryCache.set(hash, cacheResult);
return cacheResult;
}
}
};
function grafast(args, legacyResolvedPreset, legacyCtx) {
// Convert legacy args to properties on `args`:
if (legacyResolvedPreset !== undefined) {
args.resolvedPreset = args.resolvedPreset ?? legacyResolvedPreset;
}
if (legacyCtx !== undefined) {
args.requestContext = args.requestContext ?? legacyCtx;
}
const { schema, source, rootValue, contextValue, variableValues, operationName, onError, extensions, fieldResolver, typeResolver, resolvedPreset, requestContext, middleware: rawMiddleware, } = args;
const middleware = rawMiddleware !== undefined
? rawMiddleware
: resolvedPreset != null
? (0, middleware_js_1.getGrafastMiddleware)(resolvedPreset)
: null;
// Validate Schema
const schemaValidationErrors = middleware != null && resolvedPreset != null
? middleware.runSync("validateSchema", { schema, resolvedPreset }, validateSchemaWithEvent)
: validateSchema(schema);
if (schemaValidationErrors.length > 0) {
return { errors: schemaValidationErrors };
}
// Cached parse and validate
const documentOrErrors = middleware != null && resolvedPreset != null
? middleware.runSync("parseAndValidate", { resolvedPreset, schema, source }, parseAndValidateWithEvent)
: parseAndValidate(schema, source);
if (Array.isArray(documentOrErrors)) {
return { errors: documentOrErrors };
}
const document = documentOrErrors;
const executionArgs = {
schema,
document,
rootValue,
contextValue,
variableValues,
operationName,
onError,
extensions,
fieldResolver,
typeResolver,
middleware,
resolvedPreset,
requestContext,
};
if (resolvedPreset && requestContext) {
const argsOrPromise = (0, index_js_1.hookArgs)(executionArgs);
if ((0, utils_js_1.isPromiseLike)(argsOrPromise)) {
return Promise.resolve(argsOrPromise).then(execute_js_1.execute);
}
else {
return (0, execute_js_1.execute)(argsOrPromise);
}
}
else {
return (0, execute_js_1.execute)(executionArgs);
}
}
function grafastSync(args, legacyResolvedPreset, legacyRequestContext) {
const result = grafast(args, legacyResolvedPreset, legacyRequestContext);
if ((0, utils_js_1.isPromiseLike)(result)) {
throw new error_js_1.SafeError("Grafast execution failed to complete synchronously.");
}
return result;
}
function validateSchemaWithEvent(event) {
return validateSchema(event.schema);
}
function parseAndValidateWithEvent(event) {
return parseAndValidate(event.schema, event.source);
}
//# sourceMappingURL=grafastGraphql.js.map