UNPKG

grafast

Version:

Cutting edge GraphQL planning and execution engine

247 lines 11.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.withFieldArgsForArguments = withFieldArgsForArguments; exports.getNullableInputTypeAtPath = getNullableInputTypeAtPath; const tslib_1 = require("tslib"); const graphql = tslib_1.__importStar(require("graphql")); const inspect_js_1 = require("./inspect.js"); const applyInput_js_1 = require("./steps/applyInput.js"); const bakedInput_js_1 = require("./steps/bakedInput.js"); const constant_js_1 = require("./steps/constant.js"); const object_js_1 = require("./steps/object.js"); const utils_js_1 = require("./utils.js"); const { getNullableType, isInputObjectType, isListType } = graphql; function assertNotRuntime(operationPlan, description) { if (operationPlan.phase === "ready") { throw new Error(`${description} may only be called at planning time; however you have code that has attempted to call it during execution time. Please revisit your plan resolvers to locate the issue.`); } } function withFieldArgsForArguments(operationPlan, trackedArguments, field, $parent, applyAfterMode, coordinate, callback) { if (operationPlan.loc !== null) operationPlan.loc.push(`withFieldArgsForArguments(${field.name})`); const args = Object.create(null); for (const arg of field.args) { args[arg.name] = arg; } const applied = new Map(); let autoApplyDisabled = false; const fieldArgs = { typeAt(path) { if (typeof path === "string") { return args[path].type; } else { if (path.length === 0) { throw new Error(`typeAt can only be used with a non-empty path since arguments themselves don't belong to a type but a field.`); } const argName = path[0]; let type = args[argName]?.type; if (!type) { throw new Error(`Argument ${argName} does not exist`); } for (let i = 1, l = path.length; i < l; i++) { const segment = path[i]; type = graphql.isNonNullType(type) ? type.ofType : type; if (typeof segment === "number") { if (!isListType(type)) { throw new Error(`Invalid path ${path.slice(1)} within argument ${argName}; expected a list at path index ${i - 1}`); } type = type.ofType; } else { if (!isInputObjectType(type)) { throw new Error(`Invalid path ${path.slice(1)} within argument ${argName}; expected an object at path index ${i - 1}`); } const arg = type.getFields()[segment]; if (!arg) { throw new Error(`Invalid path ${path.slice(1)} within argument ${argName}; ${type} does not have a field '${segment}'`); } type = arg.type; } } return type; } }, getRaw(path) { assertNotRuntime(operationPlan, `fieldArgs.getRaw()`); if (path === undefined) { return (0, object_js_1.object)(trackedArguments); } else if (typeof path === "string") { return trackedArguments[path]; } else if (Array.isArray(path)) { const [first, ...rest] = path; if (!first) { throw new Error(`getRaw() must be called with a non-empty path`); } let $entry = trackedArguments[first]; for (const pathSegment of rest) { if (typeof pathSegment === "number" && "at" in $entry) { $entry = $entry.at(pathSegment); } else if ("get" in $entry) { $entry = $entry.get(pathSegment); } else { throw new Error(`'getRaw' path must only relate to input objects right now; path was: '${path}' (failed at '${pathSegment}')`); } } return $entry; } else { throw new Error(`Invalid path passed to FieldArgs.getRaw(); please check your code. Path: ${(0, inspect_js_1.inspect)(path)}`); } }, getBaked(inPath) { const path = typeof inPath === "string" ? [inPath] : inPath; const $raw = this.getRaw(path); const inputType = this.typeAt(path); const $baked = (0, bakedInput_js_1.bakedInput)(inputType, $raw); return $baked; }, autoApply($target) { if (!autoApplyDisabled) { autoApplyDisabled = true; processAfter($parent, fieldArgs, $target, args, applyAfterMode, coordinate); } }, apply($target, inPathOrGetTargetFromParent, maybeGetTargetFromParent) { const inPath = typeof inPathOrGetTargetFromParent === "function" ? undefined : inPathOrGetTargetFromParent; const getTargetFromParent = typeof inPathOrGetTargetFromParent === "function" ? inPathOrGetTargetFromParent : maybeGetTargetFromParent; assertNotRuntime(operationPlan, `fieldArgs.apply()`); const path = Array.isArray(inPath) ? inPath : inPath ? [inPath] : []; const pathString = path.join("."); const $existing = applied.get(pathString); if ($existing) { throw new Error(`Attempted to apply 'applyPlan' at input path ${pathString} more than once - first time to ${$existing}, second time to ${$target}. Multiple applications are not currently supported.`); } if (path.length === 0) { autoApplyDisabled = true; // Auto-apply all the arguments for (const argName of Object.keys(args)) { fieldArgs.apply($target, [argName]); } } else { const [argName, ...rest] = path; if (typeof argName !== "string") { throw new Error(`Invalid path; argument '${argName}' is an invalid argument name`); } const arg = args[argName]; if (!arg) { throw new Error(`Invalid path; argument '${argName}' does not exist`); } const typeAtPath = getNullableInputTypeAtPath(arg.type, rest); const $valueAtPath = fieldArgs.getRaw(inPath); if ($valueAtPath instanceof constant_js_1.ConstantStep && $valueAtPath.data === undefined) { // Skip applying! } else { $target.apply((0, applyInput_js_1.applyInput)(typeAtPath, $valueAtPath, getTargetFromParent)); } } }, }; for (const argName of Object.keys(args)) { let val; Object.defineProperty(fieldArgs, `$${argName}`, { get() { return (val ??= fieldArgs.getRaw(argName)); }, }); } const result = callback(fieldArgs); if (result === undefined) { throw new Error(`Field ${coordinate} returned 'undefined'; perhaps you forgot the 'return' statement?`); } (0, utils_js_1.assertNotPromise)(result, callback, operationPlan.loc?.join(">") ?? "???"); if (result != null) { fieldArgs.autoApply(result); } if (operationPlan.loc !== null) operationPlan.loc.pop(); return result; } function processAfter($parent, rootFieldArgs, $result, args, applyAfterMode, coordinate) { const schema = $parent.operationPlan.schema; for (const [argName, arg] of Object.entries(args)) { const autoApply = applyAfterMode === "plan" ? arg.extensions.grafast?.applyPlan : applyAfterMode === "subscribePlan" ? arg.extensions.grafast?.applySubscribePlan : null; if (autoApply) { if (arg.defaultValue === undefined) { const $argVal = rootFieldArgs.getRaw(argName); if ($argVal instanceof constant_js_1.ConstantStep && $argVal.data === undefined) { // no action necessary continue; } } // TODO: should this have dollars on it for accessing subkeys? const input = { typeAt(path) { return rootFieldArgs.typeAt(concatPath(argName, path)); }, getRaw(path) { return rootFieldArgs.getRaw(concatPath(argName, path)); }, apply($target, pathOrTargetGetter, maybeTargetGetter) { if (typeof pathOrTargetGetter === "function") { return rootFieldArgs.apply($target, [argName], pathOrTargetGetter); } else { return rootFieldArgs.apply($target, concatPath(argName, pathOrTargetGetter), maybeTargetGetter); } }, }; const result = autoApply($parent, $result, input, { schema, arg, argName, }); if (result !== undefined) { const fullCoordinate = `${coordinate}(${argName}:)`; throw new Error(`Argument ${fullCoordinate}'s applyPlan returned a value. This may indicate a bug in that method, please see https://err.red/gaap#coord=${encodeURIComponent(fullCoordinate)}`); } } } } function getNullableInputTypeAtPath(startType, path) { let type = getNullableType(startType); for (let i = 0, l = path.length; i < l; i++) { const segment = path[i]; if (typeof segment === "number") { // Expect list if (!isListType(type)) { throw new Error(`Invalid path passed to fieldArgs.get(); expected list type, but found ${type}`); } type = getNullableType(type.ofType); } else { // Must be a string if (!isInputObjectType(type)) { throw new Error(`Invalid path passed to fieldArgs.get(); expected object type, but found ${type}`); } const field = type.getFields()[segment]; if (!field) { throw new Error(`Invalid path passed to fieldArgs.get(); ${type} has no field named ${segment}`); } type = getNullableType(field.type); } } return type; } function concatPath(argName, subpath) { if (subpath == null) return [argName]; const localPath = Array.isArray(subpath) ? subpath : [subpath]; return [argName, ...localPath]; } //# sourceMappingURL=operationPlan-input.js.map