UNPKG

grafast

Version:

Cutting edge GraphQL planning and execution engine

225 lines 10.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.assertInputStep = assertInputStep; exports.graphqlGetTypeForNode = graphqlGetTypeForNode; exports.inputStep = inputStep; const tslib_1 = require("tslib"); const graphql = tslib_1.__importStar(require("graphql")); const inspect_js_1 = require("./inspect.js"); const __inputDefault_js_1 = require("./steps/__inputDefault.js"); const __inputDynamicScalar_js_1 = require("./steps/__inputDynamicScalar.js"); const __inputObject_js_1 = require("./steps/__inputObject.js"); const __inputStaticLeaf_js_1 = require("./steps/__inputStaticLeaf.js"); const index_js_1 = require("./steps/index.js"); const utils_js_1 = require("./utils.js"); const { assertScalarType, GraphQLError, GraphQLList, GraphQLNonNull, isInputType, isLeafType, isInputObjectType, Kind, } = graphql; function assertInputStep(itemPlan) { if (itemPlan instanceof index_js_1.__TrackedValueStep) return; if (itemPlan instanceof index_js_1.__InputListStep) return; if (itemPlan instanceof index_js_1.__InputStaticLeafStep) return; if (itemPlan instanceof __inputObject_js_1.__InputObjectStep) return; throw new Error(`Expected an InputStep, but found ${itemPlan}`); } function graphqlGetTypeForNode(operationPlan, node) { switch (node.kind) { case Kind.NAMED_TYPE: { const type = operationPlan.schema.getType(node.name.value); if (!type) { // Should not happen since the GraphQL operation has already been // validated against the schema. throw new Error(`Could not find type with name '${node.name.value}' in the schema`); } return type; } case Kind.LIST_TYPE: return new GraphQLList(graphqlGetTypeForNode(operationPlan, node.type)); case Kind.NON_NULL_TYPE: return new GraphQLNonNull(graphqlGetTypeForNode(operationPlan, node.type)); default: { const never = node; throw new Error(`Unknown node kind; node: ${(0, inspect_js_1.inspect)(never)}`); } } } /** * Returns a plan for the given `rawInputValue` AST node which could be a * variable or a literal, and could be nested so that a variable (or more than * one) appears somewhere. More than one plan may be created. * * @internal */ function inputStep(operationPlan, inputType, inputValue, defaultValue = undefined) { let c1 = operationPlan._inputStepCache.get(inputType); if (!c1) { c1 = new Map(); operationPlan._inputStepCache.set(inputType, c1); } let c2 = c1.get(inputValue); if (!c2) { c2 = new Map(); c1.set(inputValue, c2); } let c3 = c2.get(defaultValue); if (c3) return c3; c3 = _inputStep(operationPlan, inputType, inputValue, defaultValue); c2.set(defaultValue, c3); return c3; } function _inputStep(operationPlan, inputType, inputValue, defaultValue) { const { valueNodeToStaticValueCache } = operationPlan; if (inputValue === undefined) { // Definitely can't be or contain a variable! if (defaultValue === undefined) { return (0, index_js_1.constant)(undefined); } else { return valueNodeToCachedStaticValueConstantStep(valueNodeToStaticValueCache, defaultValue, inputType); } } const isObj = isInputObjectType(inputType); if (inputValue.kind === "Variable") { // Note: this is the only other place where `defaultValue` might be used // we know `inputValue` is not a variable. const variableName = inputValue.name.value; const variableDefinition = operationPlan.operation.variableDefinitions?.find((def) => def.variable.name.value === variableName); if (!variableDefinition) { // Should not happen since the GraphQL operation has already been // validated. throw new Error(`No definition for variable '${variableName}' found`); } const variableType = graphqlGetTypeForNode(operationPlan, variableDefinition.type); if (!isInputType(variableType)) { throw new Error(`Expected varible type to be an input type`); } const variableWillDefinitelyBeSet = variableType instanceof GraphQLNonNull || variableDefinition.defaultValue != null; return inputVariablePlan(operationPlan, variableName, variableType, inputType, defaultValue, variableWillDefinitelyBeSet); } else if (inputType instanceof GraphQLNonNull) { const innerType = inputType.ofType; if (inputValue.kind === Kind.NULL) { throw new Error(`Null found in non-null position; this should have been prevented by validation.`); } const valuePlan = inputStep(operationPlan, innerType, inputValue, undefined); return inputNonNullPlan(operationPlan, valuePlan); } else if (inputValue.kind === Kind.NULL) { return (0, index_js_1.constant)(null); } else if (inputType instanceof GraphQLList) { const variableNames = new Set(); (0, utils_js_1.findVariableNamesUsedInValueNode)(inputValue, variableNames); if (variableNames.size === 0) { return valueNodeToCachedStaticValueConstantStep(valueNodeToStaticValueCache, inputValue, inputType); } return new index_js_1.__InputListStep(inputType, inputValue); } else if (isLeafType(inputType)) { if (inputValue?.kind === Kind.OBJECT || inputValue?.kind === Kind.LIST) { const scalarType = assertScalarType(inputType); return new __inputDynamicScalar_js_1.__InputDynamicScalarStep(scalarType, inputValue); } else { // Variable is already ruled out, so it must be one of: Kind.INT | Kind.FLOAT | Kind.STRING | Kind.BOOLEAN | Kind.NULL | Kind.ENUM // none of which can contain a variable: return (0, __inputStaticLeaf_js_1.__inputStaticLeaf)(inputType, inputValue); } } else if (isObj) { return new __inputObject_js_1.__InputObjectStep(inputType, inputValue); } else { const never = inputType; throw new Error(`Unsupported type in inputPlan: '${(0, inspect_js_1.inspect)(never)}'`); } } function doTypesMatch(variableType, expectedType) { if (variableType instanceof GraphQLNonNull && expectedType instanceof GraphQLNonNull) { return doTypesMatch(variableType.ofType, expectedType.ofType); } else if (variableType instanceof GraphQLNonNull) { // Variable is stricter than input type, that's fine return doTypesMatch(variableType.ofType, expectedType); } else if (variableType instanceof GraphQLList && expectedType instanceof GraphQLList) { return doTypesMatch(variableType.ofType, expectedType.ofType); } else { return variableType === expectedType; } } /** * Returns a step representing a variable's value. * * @param operationPlan - * @param variableName - * @param variableType - * @param inputType - * @param defaultValue - * @param variableWillDefinitelyBeSet - If `true` the variable is either * non-null _or_ it has a default value (including null). In this case, the * variable will never be `undefined` and thus an input position's defaultValue * will never be invoked where it is used. */ function inputVariablePlan(operationPlan, variableName, variableType, inputType, defaultValue, variableWillDefinitelyBeSet) { if (variableType instanceof GraphQLNonNull && !(inputType instanceof GraphQLNonNull)) { const unwrappedVariableType = variableType.ofType; return inputVariablePlan(operationPlan, variableName, unwrappedVariableType, inputType, defaultValue, variableWillDefinitelyBeSet); } const typesMatch = doTypesMatch(variableType, inputType); if (!typesMatch) { // REF: https://spec.graphql.org/draft/#IsVariableUsageAllowed() if (inputType instanceof GraphQLNonNull && !(variableType instanceof GraphQLNonNull)) { const variablePlan = inputVariablePlan(operationPlan, variableName, variableType, inputType.ofType, defaultValue, variableWillDefinitelyBeSet); // TODO: find a way to do this without doing eval. For example: track list of variables that may not be nullish. if (variablePlan.evalIs(null) || (!variableWillDefinitelyBeSet && variablePlan.evalIs(undefined))) { throw new GraphQLError(`Expected non-null value of type ${inputType.ofType.toString()}`); } return variablePlan; } throw new Error("Expected variable and input types to match"); } const $variableValue = operationPlan.trackedVariableValuesStep.get(variableName); if (defaultValue === undefined) { // There's no default value and thus the default will not be used; use the variable. return $variableValue; } else if (variableWillDefinitelyBeSet) { // The variable will DEFINITELY be set (even if it is set to null, possibly // by a default), so the input position's default value will never apply. return $variableValue; } else { // Here: // - the variable is nullable, optional, and has no default value // - the input position has a default value // We thus need a step that results in `variableValue === undefined ? defaultValue : variableValue` const runtimeDefaultValue = (0, utils_js_1.valueNodeToStaticValue)(defaultValue, inputType); return new __inputDefault_js_1.__InputDefaultStep($variableValue, runtimeDefaultValue); } } /** * Implements `InputNonNullStep`. */ function inputNonNullPlan(_operationPlan, innerPlan) { return innerPlan; } function valueNodeToCachedStaticValueConstantStep(cache, valueNode, inputType) { let step = cache.get(valueNode); if (!step) { step = (0, index_js_1.constant)((0, utils_js_1.valueNodeToStaticValue)(valueNode, inputType), false); cache.set(valueNode, step); } return step; } //# sourceMappingURL=input.js.map