@neo4j/graphql
Version:
A GraphQL to Cypher query execution layer for Neo4j and JavaScript GraphQL implementations
277 lines • 12.5 kB
JavaScript
"use strict";
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.FieldFactory = void 0;
const get_entity_adapter_1 = require("../../../schema-model/utils/get-entity-adapter");
const deep_merge_1 = require("../../../utils/deep-merge");
const utils_1 = require("../../../utils/utils");
const check_authentication_1 = require("../../authorization/check-authentication");
const OperationField_1 = require("../ast/fields/OperationField");
const AggregationAttributeField_1 = require("../ast/fields/aggregation-fields/AggregationAttributeField");
const CountField_1 = require("../ast/fields/aggregation-fields/CountField");
const DeprecatedAggregationAttributeField_1 = require("../ast/fields/aggregation-fields/DeprecatedAggregationAttributeField");
const DeprecatedCountField_1 = require("../ast/fields/aggregation-fields/DeprecatedCountField");
const AttributeField_1 = require("../ast/fields/attribute-fields/AttributeField");
const DateTimeField_1 = require("../ast/fields/attribute-fields/DateTimeField");
const is_concrete_entity_1 = require("../utils/is-concrete-entity");
const find_fields_by_name_in_fields_by_type_name_field_1 = require("./parsers/find-fields-by-name-in-fields-by-type-name-field");
const parse_selection_set_fields_1 = require("./parsers/parse-selection-set-fields");
class FieldFactory {
constructor(queryASTFactory) {
this.queryASTFactory = queryASTFactory;
}
createFields(entity, rawFields, context) {
const fieldsToMerge = (0, utils_1.filterTruthy)(Object.values(rawFields).map((field) => {
const { fieldName } = (0, parse_selection_set_fields_1.parseSelectionSetField)(field.name);
return this.getRequiredResolveTree({
entity,
fieldName,
});
}));
const mergedFields = (0, deep_merge_1.deepMerge)([rawFields, ...fieldsToMerge]);
const fields = Object.values(mergedFields).flatMap((field) => {
const { fieldName, isConnection, isAggregation } = (0, parse_selection_set_fields_1.parseSelectionSetField)(field.name);
if ((0, is_concrete_entity_1.isConcreteEntity)(entity)) {
// TODO: Move this to the tree
(0, check_authentication_1.checkEntityAuthentication)({
entity: entity.entity,
targetOperations: ["READ"],
context,
field: field.name,
});
const attribute = entity.findAttribute(fieldName);
if (attribute) {
const customResolver = attribute.annotations.customResolver;
if (customResolver) {
// We don't want to project the custom resolver field itself
return;
}
}
const relationship = entity.findRelationship(fieldName);
if (relationship) {
if (isConnection) {
return this.createConnectionField(relationship, field, context);
}
if (isAggregation) {
return this.createRelationshipAggregationField(relationship, field, context);
}
return this.createRelationshipField({ relationship, field, context });
}
if (!relationship && (isConnection || isAggregation)) {
throw new Error(`Relationship ${fieldName} not found in entity ${entity.name}`);
}
}
return this.createAttributeField({
entity,
fieldName,
field,
context,
});
});
return (0, utils_1.filterTruthy)(fields);
}
createRelationshipAggregationField(relationship, resolveTree, context) {
const operation = this.queryASTFactory.operationsFactory.createAggregationOperation(relationship, resolveTree, context);
return new OperationField_1.OperationField({
alias: resolveTree.alias,
operation,
});
}
createAggregationFields(entity, rawFields, useDeprecatedAttribute = false) {
return (0, utils_1.filterTruthy)(Object.values(rawFields).map((field) => {
if (field.name === "count") {
const countFields = field.fieldsByTypeName["Count"] ?? field.fieldsByTypeName["CountConnection"];
if (countFields) {
const hasNodes = (0, find_fields_by_name_in_fields_by_type_name_field_1.findFieldsByNameInFieldsByTypeNameField)(countFields, "nodes").length > 0;
const hasEdges = (0, find_fields_by_name_in_fields_by_type_name_field_1.findFieldsByNameInFieldsByTypeNameField)(countFields, "edges").length > 0;
return new CountField_1.CountField({
alias: field.alias,
entity: entity,
fields: {
nodes: hasNodes,
edges: hasEdges,
},
});
}
return new DeprecatedCountField_1.DeprecatedCountField({
alias: field.alias,
entity: entity,
});
}
else {
const attribute = entity.findAttribute(field.name);
if (!attribute) {
throw new Error(`Attribute ${field.name} not found`);
}
const aggregateFields = field.fieldsByTypeName[attribute.getAggregateSelectionTypeName()] || {};
const aggregationProjection = Object.values(aggregateFields).reduce((acc, f) => {
acc[f.name] = f.alias;
return acc;
}, {});
if (useDeprecatedAttribute) {
return new DeprecatedAggregationAttributeField_1.DeprecatedAggregationAttributeField({
attribute,
alias: field.alias,
aggregationProjection,
});
}
else {
return new AggregationAttributeField_1.AggregationAttributeField({
attribute,
alias: field.alias,
aggregationProjection,
});
}
}
}));
}
getRequiredResolveTree({ entity, fieldName, }) {
const attribute = entity.findAttribute(fieldName);
if (!attribute) {
return;
}
const customResolver = attribute.annotations.customResolver;
if (!customResolver) {
return;
}
return customResolver.parsedRequires;
}
createAttributeField({ entity, fieldName, field, context, }) {
if (["cursor", "node"].includes(fieldName)) {
return;
}
const attribute = entity.findAttribute(fieldName);
if (fieldName === "id" && !attribute && (0, is_concrete_entity_1.isConcreteEntity)(entity)) {
const globalIdAttribute = entity.globalIdField;
if (!globalIdAttribute) {
throw new Error(`attribute ${fieldName} not found`);
}
return new AttributeField_1.AttributeField({ alias: globalIdAttribute.name, attribute: globalIdAttribute });
}
if (!attribute) {
throw new Error(`attribute ${fieldName} not found`);
}
const cypherAnnotation = attribute.annotations.cypher;
if (cypherAnnotation) {
return this.createCypherAttributeField({
field,
attribute,
context,
cypherAnnotation,
});
}
if (attribute.typeHelper.isDateTime()) {
return new DateTimeField_1.DateTimeField({
attribute,
alias: field.alias,
});
}
return new AttributeField_1.AttributeField({ alias: field.alias, attribute });
}
createCypherAttributeField({ field, attribute, context, cypherAnnotation, }) {
const typeName = attribute.typeHelper.isList() ? attribute.type.ofType.name : attribute.type.name;
const rawFields = field.fieldsByTypeName[typeName];
const extraParams = {};
if (cypherAnnotation.statement.includes("$jwt") && context.authorization.jwtParam) {
extraParams.jwt = context.authorization.jwtParam.value;
}
// move the user specified arguments in a different object as they should be treated as arguments of a Cypher Field
const cypherArguments = { ...field.args };
field.args = {};
if (rawFields) {
if (attribute.typeHelper.isObject()) {
const concreteEntity = this.queryASTFactory.schemaModel.getConcreteEntityAdapter(typeName);
return this.createCypherOperationField({
target: concreteEntity,
field,
context,
cypherAttributeField: attribute,
cypherArguments,
});
}
else if (attribute.typeHelper.isAbstract()) {
const entity = this.queryASTFactory.schemaModel.getEntity(typeName);
const targetEntity = entity ? (0, get_entity_adapter_1.getEntityAdapter)(entity) : undefined;
return this.createCypherOperationField({
target: targetEntity,
field,
context,
cypherAttributeField: attribute,
cypherArguments,
});
}
}
return this.createCypherOperationField({
field,
context,
cypherAttributeField: attribute,
cypherArguments,
});
}
createConnectionOperation(relationship, target, resolveTree, context) {
if ((0, is_concrete_entity_1.isConcreteEntity)(target)) {
return this.queryASTFactory.operationsFactory.createConnectionOperationAST({
relationship,
target,
resolveTree,
context,
});
}
return this.queryASTFactory.operationsFactory.createCompositeConnectionOperationAST({
relationship,
target,
resolveTree,
context,
});
}
createConnectionField(relationship, field, context) {
const connectionOp = this.createConnectionOperation(relationship, relationship.target, field, context);
return new OperationField_1.OperationField({
operation: connectionOp,
alias: field.alias,
});
}
createCypherOperationField({ target, field, context, cypherAttributeField, cypherArguments, }) {
const cypherOp = this.queryASTFactory.operationsFactory.createCustomCypherOperation({
resolveTree: field,
context,
entity: target,
cypherAttributeField,
cypherArguments,
});
return new OperationField_1.OperationField({
operation: cypherOp,
alias: field.alias,
});
}
createRelationshipField({ relationship, field, context, }) {
const operation = this.queryASTFactory.operationsFactory.createReadOperation({
entityOrRel: relationship,
resolveTree: field,
context,
});
return new OperationField_1.OperationField({
operation,
alias: field.alias,
});
}
}
exports.FieldFactory = FieldFactory;
//# sourceMappingURL=FieldFactory.js.map