UNPKG

grafast

Version:

Cutting edge GraphQL planning and execution engine

334 lines 12.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.__TrackedValueStep = void 0; const graphql_1 = require("graphql"); const index_js_1 = require("../index.js"); const step_js_1 = require("../step.js"); /** * Implements the `__TrackedValueStep(operationPlan, object, constraints, path)` * algorithm used to allow runtime AND plan-time access to the three special * entities: `variableValues`, `rootValue` and `context`. * * ExecutableStep-time access can evaluate the `object` passed to the constructor, and * will add constraints to the relevant operationPlan.variableValuesConstraints, * operationPlan.rootValueConstraints or operationPlan.contextConstraints to * ensure that all future variableValues, rootValues and context will match the * assumptions made. * * Run-time access will see the runtime values of these properties, it will * **NOT** reference the `object` passed to the constructor. * * In core this will be used for evaluating `@skip`, `@include`, `@defer` and * `@stream` directives so that a different OpPlan will be used if these would * change the query plan, but it can also be used within plan resolvers to * branch the logic of a plan based on something in these entities. */ class __TrackedValueStep extends step_js_1.UnbatchedStep { static { this.$$export = { moduleName: "grafast", exportName: "__TrackedValueStep", }; } static withGraphQLType(value, valuePlan, constraints, path = [], graphqlType) { return (0, index_js_1.operationPlan)().withRootLayerPlan(() => new __TrackedValueStep(value, valuePlan, constraints, path, true, graphqlType)); } /** * @internal */ constructor(value, valuePlan, constraints, path = [], isImmutable, graphqlTypeOrVariableDefinitions) { super(); this.isSyncAndSafe = true; this._isImmutable = isImmutable; this.addDependency(valuePlan); this._initialValue = value; this.constraints = constraints; this.path = path; this.nullableGraphQLType = graphqlTypeOrVariableDefinitions && !isArray(graphqlTypeOrVariableDefinitions) ? (0, graphql_1.getNullableType)(graphqlTypeOrVariableDefinitions) : undefined; this.variableDefinitions = graphqlTypeOrVariableDefinitions && isArray(graphqlTypeOrVariableDefinitions) ? graphqlTypeOrVariableDefinitions : undefined; if ((0, graphql_1.isInputObjectType)(this.nullableGraphQLType)) { const fields = this.nullableGraphQLType.getFields(); for (const fieldName of Object.keys(fields)) { let step; Object.defineProperty(this, `$${fieldName}`, { get: () => { if (!step) { step = this.get(fieldName); } return step; }, }); } } else if (this.variableDefinitions) { for (const def of this.variableDefinitions) { const varName = def.variable.name.value; let step; Object.defineProperty(this, `$${varName}`, { get: () => { if (!step) { step = this.get(varName); } return step; }, }); } } } execute({ count, values: [values0], }) { // We have only one dependency, return the value of that. return values0.isBatch ? values0.entries : (0, index_js_1.arrayOfLength)(count, values0.value); } unbatchedExecute(_extra, v) { return v; } getValuePlan() { return this.getDep(0, true); } /** * Get the named property of an object. */ get(attrName) { if (this._isImmutable) { return this.operationPlan.cacheImmutableStep(`__TrackedValue${this.id}.get`, attrName, () => this._get(attrName)); } else { return this._get(attrName); } } _get(attrName) { const { _initialValue: value, path, constraints } = this; const newValue = value?.[attrName]; const newValuePlan = this.getValuePlan().get(attrName); const newPath = [...path, attrName]; if (this.nullableGraphQLType) { if ((0, graphql_1.isInputObjectType)(this.nullableGraphQLType)) { const fields = this.nullableGraphQLType.getFields(); const field = fields[attrName]; if (!field) { throw new Error(`'${this.nullableGraphQLType}' has no attribute '${attrName}'`); } return __TrackedValueStep.withGraphQLType(newValue, newValuePlan, constraints, newPath, field.type); } else { throw new Error(`Cannot get field '${attrName}' on non-input-object type '${this.nullableGraphQLType}'`); } } else if (this.variableDefinitions) { const def = this.variableDefinitions.find((d) => d.variable.name.value === attrName); if (!def) { throw new Error(`No variable named '$${attrName}' exists in this operation`); } const getType = (t) => { if (t.kind === graphql_1.Kind.NON_NULL_TYPE) { return new graphql_1.GraphQLNonNull(getType(t.type)); } else if (t.kind === graphql_1.Kind.LIST_TYPE) { return new graphql_1.GraphQLList(getType(t.type)); } else { const name = t.name.value; return this.operationPlan.schema.getType(name); } }; const type = getType(def.type); return __TrackedValueStep.withGraphQLType(newValue, newValuePlan, constraints, newPath, type); } else if (this._isImmutable) { return this.operationPlan.withRootLayerPlan(() => new __TrackedValueStep(newValue, newValuePlan, constraints, newPath, this._isImmutable)); } else { return new __TrackedValueStep(newValue, newValuePlan, constraints, newPath, this._isImmutable); } } /** * Get the entry at the given index in an array. */ at(index) { if (this._isImmutable) { return this.operationPlan.cacheImmutableStep(`__TrackedValue${this.id}.at`, index, () => this._at(index)); } else { return this._at(index); } } _at(index) { const { _initialValue: value, path, constraints } = this; const newValue = value?.[index]; const newValuePlan = this.getValuePlan().at(index); const newPath = [...path, index]; if (this.nullableGraphQLType) { if ((0, graphql_1.isListType)(this.nullableGraphQLType)) { return __TrackedValueStep.withGraphQLType(newValue, newValuePlan, constraints, newPath, this.nullableGraphQLType.ofType); } else { throw new Error(`'${this.nullableGraphQLType}' is not a list type, cannot access array index '${index}' on it`); } } else if (this._isImmutable) { return this.operationPlan.withRootLayerPlan(() => new __TrackedValueStep(newValue, newValuePlan, constraints, newPath, this._isImmutable)); } else { return new __TrackedValueStep(newValue, newValuePlan, constraints, newPath, this._isImmutable); } } /** * Evaluates the current value, and adds a constraint to the OpPlan to ensure * that all future evaluations of this property will always return the same * value. * * **WARNING**: avoid using this where possible, it causes OpPlans to split. * * **WARNING**: this is the most expensive eval, if you need to eval, prefer evalIs, evalHas, etc instead. * * @internal */ eval() { const { path, _initialValue: value } = this; this.constraints.push({ type: "value", path, value, }); return value; } /** * Evaluates if the current value is equal to this specific value, and adds a * constraint to the OpPlan to ensure that all future evaluations of this * check will always return the same (boolean) result. * * Should only be used on scalars. * * **WARNING**: avoid using this where possible, it causes OpPlans to split. * * @internal */ evalIs(expectedValue) { const { _initialValue: value, path } = this; const pass = value === expectedValue; this.constraints.push({ type: "equal", path, expectedValue, pass, }); return pass; } /** @internal */ evalIsEmpty() { const { _initialValue: value, path } = this; const isEmpty = typeof value === "object" && value !== null && Object.keys(value).length === 0; this.constraints.push({ type: "isEmpty", path, isEmpty, }); return isEmpty; } /** * Evaluates if the current value is an object with the given key, and adds a * constraint to the OpPlan to ensure that all future evaluations of this * check will always return the same (boolean) result. * * **WARNING**: avoid using this where possible, it causes OpPlans to split. * * @internal */ evalHas(key) { const { _initialValue: value, path } = this; const newPath = [...path, key]; // NOTE: `key in value` would be more performant here, but we cannot trust // users not to pass `{foo: undefined}` so we must do the more expensive // `value[key] !== undefined` check. const exists = (typeof value === "object" && value && value[key] !== undefined) || false; this.constraints.push({ type: "exists", path: newPath, exists, }); return exists; } /** * Evaluates the keys of the current value, and adds a * constraint to the OpPlan to ensure that all future evaluations of this * check will always return the same result. * * **WARNING**: avoid using this where possible, it causes OpPlans to split. * * @internal */ evalKeys() { const { _initialValue: value, path } = this; if (!(0, graphql_1.isInputObjectType)(this.nullableGraphQLType)) { throw new Error("evalKeys must only be called for object types"); } if (value == null) { this.constraints.push({ type: "keys", path, keys: null, }); return null; } const keys = []; const keysOfType = Object.keys(this.nullableGraphQLType.getFields()); // NOTE: it's important that we loop through the fields in their defined // order, this ensures the `keys` array always has consistent ordering. for (let i = 0; i < keysOfType.length; i++) { const key = keysOfType[i]; // NOTE: `key in value` would be more performant here, but we cannot trust // users not to pass `{foo: undefined}` so we must do the more expensive // `value[key] !== undefined` check. if (value[key] !== undefined) { keys.push(key); } } this.constraints.push({ type: "keys", path, keys, }); return keys; } /** * Evaluates the length of the current value (assumed to be an array), and * adds a constraint to the OpPlan to ensure that all future values will have * the same length. * * **WARNING**: avoid using this where possible, it causes OpPlans to split. * * @internal */ evalLength() { const { _initialValue: value, path } = this; const length = Array.isArray(value) ? value.length : null; this.constraints.push({ type: "length", path, expectedLength: length, }); return length; } // At runtime, __TrackedValueStep doesn't need to exist optimize() { return this.getDep(0); } } exports.__TrackedValueStep = __TrackedValueStep; function isArray(t) { return Array.isArray(t); } //# sourceMappingURL=__trackedValue.js.map