UNPKG

@neo4j/graphql

Version:

A GraphQL to Cypher query execution layer for Neo4j and JavaScript GraphQL implementations

277 lines 12.5 kB
"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