UNPKG

@neo4j/graphql

Version:

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

271 lines 13 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.OperationsFactory = void 0; const neo4j_driver_1 = require("neo4j-driver"); const deep_merge_1 = require("../../../utils/deep-merge"); const utils_1 = require("../../../utils/utils"); const is_concrete_entity_1 = require("../utils/is-concrete-entity"); const is_interface_entity_1 = require("../utils/is-interface-entity"); const is_union_entity_1 = require("../utils/is-union-entity"); const AggregateFactory_1 = require("./Operations/AggregateFactory"); const ConnectFactory_1 = require("./Operations/ConnectFactory"); const ConnectionFactory_1 = require("./Operations/ConnectionFactory"); const CreateFactory_1 = require("./Operations/CreateFactory"); const CustomCypherFactory_1 = require("./Operations/CustomCypherFactory"); const DeleteFactory_1 = require("./Operations/DeleteFactory"); const FulltextFactory_1 = require("./Operations/FulltextFactory"); const ReadFactory_1 = require("./Operations/ReadFactory"); const UpdateFactory_1 = require("./Operations/UpdateFactory"); const VectorFactory_1 = require("./Operations/VectorFactory"); const parse_operation_fields_1 = require("./parsers/parse-operation-fields"); const parse_selection_set_fields_1 = require("./parsers/parse-selection-set-fields"); class OperationsFactory { constructor(queryASTFactory) { this.filterFactory = queryASTFactory.filterFactory; this.fieldFactory = queryASTFactory.fieldFactory; this.sortAndPaginationFactory = queryASTFactory.sortAndPaginationFactory; this.authorizationFactory = queryASTFactory.authorizationFactory; this.createFactory = new CreateFactory_1.CreateFactory(queryASTFactory); this.connectFactory = new ConnectFactory_1.ConnectFactory(queryASTFactory); this.updateFactory = new UpdateFactory_1.UpdateFactory(queryASTFactory); this.deleteFactory = new DeleteFactory_1.DeleteFactory(queryASTFactory); this.fulltextFactory = new FulltextFactory_1.FulltextFactory(queryASTFactory); this.aggregateFactory = new AggregateFactory_1.AggregateFactory(queryASTFactory); this.customCypherFactory = new CustomCypherFactory_1.CustomCypherFactory(queryASTFactory); this.connectionFactory = new ConnectionFactory_1.ConnectionFactory(queryASTFactory); this.readFactory = new ReadFactory_1.ReadFactory(queryASTFactory); this.vectorFactory = new VectorFactory_1.VectorFactory(queryASTFactory); } createTopLevelOperation({ entity, resolveTree, context, varName, reference, }) { const operationMatch = (0, parse_operation_fields_1.parseTopLevelOperationField)(resolveTree.name, context, entity); switch (operationMatch) { case "READ": { if (!entity) { throw new Error("Entity is required for top level read operations"); } return this.readFactory.createReadOperation({ entityOrRel: entity, resolveTree, context, varName, reference, }); } case "FULLTEXT": { if (!entity) { throw new Error("Entity is required for top level connection read operations"); } if (!(0, is_concrete_entity_1.isConcreteEntity)(entity)) { throw new Error("Fulltext operations are only supported on concrete entities"); } return this.fulltextFactory.createFulltextOperation(entity, resolveTree, context); } case "VECTOR": { if (!entity) { throw new Error("Entity is required for top level connection read operations"); } if (!(0, is_concrete_entity_1.isConcreteEntity)(entity)) { throw new Error("Vector operations are only supported on concrete entities"); } return this.vectorFactory.createVectorOperation(entity, resolveTree, context); } case "CONNECTION": { if (!entity) { throw new Error("Entity is required for top level connection read operations"); } const topLevelConnectionResolveTree = this.connectionFactory.normalizeResolveTreeForTopLevelConnection(resolveTree); return this.connectionFactory.createConnectionOperationAST({ target: entity, resolveTree: topLevelConnectionResolveTree, context, }); } case "AGGREGATE": { if (!entity || (0, is_union_entity_1.isUnionEntity)(entity)) { throw new Error("Aggregate operations are not supported for Union types"); } return this.aggregateFactory.createAggregationOperationDeprecated({ entityOrRel: entity, resolveTree, context, }); } case "CUSTOM_CYPHER": { return this.customCypherFactory.createTopLevelCustomCypherOperation({ entity, resolveTree, context }); } default: { throw new Error(`Unsupported top level operation: ${resolveTree.name}`); } } } createTopLevelMutationOperation({ entity, resolveTree, context, varName, callbackBucket, resolveAsUnwind = false, }) { const operationMatch = (0, parse_operation_fields_1.parseTopLevelOperationField)(resolveTree.name, context, entity); switch (operationMatch) { case "CREATE": { (0, is_concrete_entity_1.assertIsConcreteEntity)(entity); if (resolveAsUnwind) { return this.createFactory.createUnwindCreateOperation(entity, resolveTree, context); } return this.createFactory.createCreateOperation({ entity, resolveTree, callbackBucket, context }); } case "UPDATE": { (0, is_concrete_entity_1.assertIsConcreteEntity)(entity); return this.updateFactory.createUpdateOperation(entity, resolveTree, context); } case "DELETE": { (0, is_concrete_entity_1.assertIsConcreteEntity)(entity); return this.deleteFactory.createTopLevelDeleteOperation({ entity, resolveTree, context, varName, }); } default: { throw new Error(`Unsupported top level operation: ${resolveTree.name}`); } } } /** * Proxy methods to specialized operations factories. * TODO: Refactor the following to use a generic dispatcher as done in createTopLevelOperation **/ createConnectOperation(entity, relationship, input, context) { if ((0, is_concrete_entity_1.isConcreteEntity)(entity)) { return this.connectFactory.createConnectOperation(entity, relationship, input, context); } else { return this.connectFactory.createCompositeConnectOperation(entity, relationship, input, context); } } createReadOperation(arg) { return this.readFactory.createReadOperation(arg); } getFulltextSelection(entity, context) { return this.fulltextFactory.getFulltextSelection(entity, context); } getVectorSelection(entity, context) { return this.vectorFactory.getVectorSelection(entity, context); } createAggregationOperation(entityOrRel, resolveTree, context) { return this.aggregateFactory.createAggregationOperationDeprecated({ entityOrRel, resolveTree, context }); } splitConnectionFields(rawFields) { return this.connectionFactory.splitConnectionFields(rawFields); } createConnectionOperationAST(arg) { return this.connectionFactory.createConnectionOperationAST(arg); } createCompositeConnectionOperationAST(arg) { return this.connectionFactory.createCompositeConnectionOperationAST(arg); } hydrateReadOperation(arg) { return this.readFactory.hydrateReadOperation(arg); } hydrateConnectionOperation(arg) { return this.connectionFactory.hydrateConnectionOperationAST(arg); } createCustomCypherOperation(arg) { return this.customCypherFactory.createCustomCypherOperation(arg); } /** * END of proxy methods **/ hydrateOperation({ entity, operation, whereArgs, context, paginationArgs, fieldsByTypeName, partialOf, }) { const concreteProjectionFields = { ...fieldsByTypeName[entity.name] }; // Get the abstract types of the interface const entityInterfaces = entity.compositeEntities; const interfacesFields = (0, utils_1.filterTruthy)(entityInterfaces.map((i) => fieldsByTypeName[i.name])); const projectionFields = (0, deep_merge_1.deepMerge)([...interfacesFields, concreteProjectionFields]); const fields = this.fieldFactory.createFields(entity, projectionFields, context); if (partialOf && (0, is_interface_entity_1.isInterfaceEntity)(partialOf)) { const filters = this.filterFactory.createInterfaceNodeFilters({ entity: partialOf, targetEntity: entity, whereFields: whereArgs, }); operation.addFilters(...filters); } else { const filters = this.filterFactory.createNodeFilters(entity, whereArgs); operation.addFilters(...filters); } const authFilters = this.authorizationFactory.getAuthFilters({ entity, operations: ["READ"], attributes: this.getSelectedAttributes(entity, projectionFields), context, }); operation.setFields(fields); operation.addAuthFilters(...authFilters); if (paginationArgs) { const paginationOptions = this.getOptions({ entity, limitArg: paginationArgs.limit, offsetArg: paginationArgs.offset, sortArg: paginationArgs.sort, }); if (paginationOptions) { const sort = this.sortAndPaginationFactory.createSortFields(paginationOptions, entity, context); operation.addSort(...sort); const pagination = this.sortAndPaginationFactory.createPagination(paginationOptions); if (pagination) { operation.addPagination(pagination); } } } return operation; } getOptions({ entity, limitArg, offsetArg, sortArg, }) { const limitDirective = (0, is_union_entity_1.isUnionEntity)(entity) ? undefined : entity.annotations.limit; let limit = limitArg ?? limitDirective?.default ?? limitDirective?.max; if (limit instanceof neo4j_driver_1.Integer) { limit = limit.toNumber(); } const maxLimit = limitDirective?.max; if (limit !== undefined && maxLimit !== undefined) { limit = Math.min(limit, maxLimit); } if (limit === undefined && offsetArg === undefined && sortArg === undefined) return undefined; return { limit, offset: offsetArg, sort: sortArg, }; } getSelectedAttributes(entity, rawFields) { return (0, utils_1.filterTruthy)(Object.values(rawFields).map((field) => { const { fieldName } = (0, parse_selection_set_fields_1.parseSelectionSetField)(field.name); return entity.findAttribute(fieldName); })); } getWhereArgs(resolveTree, reference) { const whereArgs = (0, utils_1.isRecord)(resolveTree.args.where) ? resolveTree.args.where : {}; if (resolveTree.name === "_entities" && reference) { const { __typename, ...referenceWhere } = reference; return { ...referenceWhere, ...whereArgs }; } return whereArgs; } } exports.OperationsFactory = OperationsFactory; //# sourceMappingURL=OperationFactory.js.map