grafast
Version:
Cutting edge GraphQL planning and execution engine
216 lines • 8.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Modifier = exports.ApplyInputStep = void 0;
exports.inputArgsApply = inputArgsApply;
exports.applyInput = applyInput;
exports.isModifier = isModifier;
exports.assertModifier = assertModifier;
exports.isApplyableStep = isApplyableStep;
const graphql_1 = require("graphql");
const global_js_1 = require("../global.js");
const step_js_1 = require("../step.js");
const constant_js_1 = require("./constant.js");
let currentModifiers = [];
let applyingModifiers = false;
let inputArgsApplyDepth = 0;
class ApplyInputStep extends step_js_1.UnbatchedStep {
static { this.$$export = {
moduleName: "grafast",
exportName: "ApplyInputStep",
}; }
constructor(inputType, $value, getTargetFromParent, $scope) {
super();
this.inputType = inputType;
this.getTargetFromParent = getTargetFromParent;
this.isSyncAndSafe = true;
this.allowMultipleOptimizations = true;
this.valueDepId = this.addUnaryDependency($value);
this.scopeDepId = $scope ? this.addUnaryDependency($scope) : null;
if (!this._isUnary) {
throw new Error(`applyInput() must be unary`);
}
this._isUnaryLocked = true;
}
deduplicate(peers) {
return peers.filter((p) => p.inputType === this.inputType &&
p.getTargetFromParent === this.getTargetFromParent);
}
optimize() {
const $value = this.getDep(this.valueDepId);
const $scope = this.scopeDepId === null ? null : this.getDep(this.scopeDepId);
if ((!$scope || $scope instanceof constant_js_1.ConstantStep) &&
$value instanceof constant_js_1.ConstantStep) {
// Replace myself with a constant!
const { operationPlan: { schema }, inputType, getTargetFromParent, } = this;
const { data } = $value;
const scope = $scope?.data;
return (0, constant_js_1.constant)(function applyInputConstant(parent) {
inputArgsApply(schema, inputType, parent, data, getTargetFromParent, scope);
}, false);
}
return this;
}
unbatchedExecute(extra, value, scope) {
const { getTargetFromParent } = this;
return (parentThing) => inputArgsApply(this.operationPlan.schema, this.inputType, parentThing, value, getTargetFromParent, scope);
}
}
exports.ApplyInputStep = ApplyInputStep;
function inputArgsApply(schema, inputType, parent, inputValue, getTargetFromParent, scope) {
try {
inputArgsApplyDepth++;
const target = getTargetFromParent
? getTargetFromParent(parent, inputValue, { scope })
: parent;
if (target != null) {
_inputArgsApply(schema, inputType, target, inputValue, scope);
}
}
finally {
inputArgsApplyDepth--;
}
let l;
if (inputArgsApplyDepth === 0 && (l = currentModifiers.length) > 0) {
applyingModifiers = true;
try {
for (let i = l - 1; i >= 0; i--) {
currentModifiers[i].apply();
}
}
finally {
applyingModifiers = false;
currentModifiers = [];
}
}
}
function applyInput(inputType, $value, getTargetFromParent) {
const opPlan = (0, global_js_1.operationPlan)();
const { schema } = opPlan;
const namedType = (0, graphql_1.getNamedType)(inputType);
return opPlan.withRootLayerPlan(() => {
const $scope = namedType.extensions?.grafast?.applyScope?.() ?? null;
if ((!$scope || $scope instanceof constant_js_1.ConstantStep) &&
$value instanceof constant_js_1.ConstantStep) {
// Replace us with a constant
const { data } = $value;
const scope = $scope?.data;
return (0, constant_js_1.constant)(function applyInputConstant(parent) {
inputArgsApply(schema, inputType, parent, data, getTargetFromParent, scope);
}, false);
}
else {
return new ApplyInputStep(inputType, $value, getTargetFromParent, $scope);
}
});
}
/*
const defaultInputObjectTypeInputPlanResolver: InputObjectTypeInputPlanResolver =
(input, info) => {
const fields = info.type.getFields();
const obj: { [key: string]: ExecutableStep } = Object.create(null);
for (const fieldName in fields) {
obj[fieldName] = input.get(fieldName);
}
return object(obj);
};
*/
function _inputArgsApply(schema, inputType, target, inputValue, scope) {
// PERF: we should have the plan generate a digest of `inputType` so that we
// can jump right to the relevant parts without too much traversal cost.
if (inputValue === undefined) {
return;
}
if ((0, graphql_1.isNonNullType)(inputType)) {
if (inputValue === null) {
throw new Error(`null value found in non-null position`);
}
_inputArgsApply(schema, inputType.ofType, target, inputValue, scope);
}
else if ((0, graphql_1.isListType)(inputType)) {
if (inputValue == null)
return;
if (!Array.isArray(inputValue)) {
throw new Error(`Expected list in list position`);
}
for (const item of inputValue) {
const itemTarget = typeof target === "function" ? target() : target;
_inputArgsApply(schema, inputType.ofType, itemTarget, item, scope);
}
}
else if (typeof target === "function") {
throw new Error("Functions may only be used as the target for list types (the function is called once per list item)");
}
else if ((0, graphql_1.isInputObjectType)(inputType)) {
if (inputValue === null) {
return;
}
const fields = inputType.getFields();
for (const [fieldName, field] of Object.entries(fields)) {
const val = inputValue[fieldName];
if (val === undefined)
continue;
if (field.extensions.grafast?.apply) {
const newTarget = field.extensions.grafast.apply(target, val, {
schema,
field,
fieldName,
scope,
});
if (newTarget != null) {
_inputArgsApply(schema, field.type, newTarget, val, scope);
}
}
}
}
else if ((0, graphql_1.isScalarType)(inputType)) {
// if (inputType.extensions.grafast?.apply) {
// }
}
else if ((0, graphql_1.isEnumType)(inputType)) {
if (inputValue === null) {
return;
}
const values = inputType.getValues();
const value = values.find((v) => v.value === inputValue);
if (value) {
if (value.extensions.grafast?.apply) {
value.extensions.grafast.apply(target, { scope, value });
}
}
else {
throw new Error(`Couldn't find value in ${inputType} for ${inputValue}`);
}
}
else {
const never = inputType;
throw new Error(`Input type expected, but found ${never}`);
}
}
/**
* Modifiers modify their parent (which may be another modifier or anything
* else). First they gather all the requirements from their children (if any)
* being applied to them, then they apply themselves to their parent. This
* application is done through the `apply()` method.
*/
class Modifier {
constructor(parent) {
this.parent = parent;
if (applyingModifiers) {
throw new Error(`Must not create new modifier whilst modifiers are being applied!`);
}
currentModifiers.push(this);
}
}
exports.Modifier = Modifier;
function isModifier(plan) {
return plan instanceof Modifier;
}
function assertModifier(plan, pathDescription) {
if (!isModifier(plan)) {
throw new Error(`The plan returned from '${pathDescription}' should be a modifier plan, but it does not implement the 'apply' method.`);
}
}
function isApplyableStep(s) {
return typeof s.apply === "function";
}
//# sourceMappingURL=applyInput.js.map