@neo4j/graphql
Version:
A GraphQL to Cypher query execution layer for Neo4j and JavaScript GraphQL implementations
271 lines • 13 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.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