UNPKG

@neo4j/graphql

Version:

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

433 lines 19.9 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. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getObjFieldMeta = getObjFieldMeta; const graphql_1 = require("graphql"); const constants_1 = require("../constants"); const parse_arguments_1 = require("../schema-model/parser/parse-arguments"); const parse_value_node_1 = require("../schema-model/parser/parse-value-node"); const upper_first_1 = require("../utils/upper-first"); const get_alias_meta_1 = __importDefault(require("./get-alias-meta")); const get_custom_resolver_meta_1 = require("./get-custom-resolver-meta"); const get_cypher_meta_1 = require("./get-cypher-meta"); const get_field_type_meta_1 = __importDefault(require("./get-field-type-meta")); const get_populated_by_meta_1 = require("./get-populated-by-meta"); const get_relationship_meta_1 = __importDefault(require("./get-relationship-meta")); function getObjFieldMeta({ obj, definitionCollection, interfaces, callbacks, customResolvers, }) { const objInterfaceNames = [...(obj.interfaces || [])]; const objects = [...definitionCollection.nodes.values()]; const unions = [...definitionCollection.unionTypes.values()]; const objInterfaces = interfaces.filter((i) => objInterfaceNames.map((n) => n.name.value).includes(i.name.value)); return obj?.fields?.reduce((res, field) => { const interfaceField = objInterfaces .find((i) => i.fields?.map((f) => f.name.value).includes(field.name.value)) ?.fields?.find((f) => f.name.value === field.name.value); // Create array of directives for this field. Field directives override interface field directives. const directives = [ ...(field?.directives || []), ...(interfaceField?.directives || []).filter((d) => !field.directives?.find((fd) => fd.name.value === d.name.value)), ]; if (directives.some((x) => x.name.value === "private")) { return res; } const relationshipMeta = (0, get_relationship_meta_1.default)(field, interfaceField); const cypherMeta = (0, get_cypher_meta_1.getCypherMeta)(field, interfaceField); const customResolverMeta = (0, get_custom_resolver_meta_1.getCustomResolverMeta)({ field, object: obj, objects, interfaces, unions, customResolvers, interfaceField, }); const typeMeta = (0, get_field_type_meta_1.default)(field.type); const idDirective = directives.find((x) => x.name.value === "id"); const relayIdDirective = directives.find((x) => x.name.value === "relayId"); const defaultDirective = directives.find((x) => x.name.value === "default"); const coalesceDirective = directives.find((x) => x.name.value === "coalesce"); const timestampDirective = directives.find((x) => x.name.value === "timestamp"); const aliasDirective = directives.find((x) => x.name.value === "alias"); const populatedByDirective = directives.find((x) => x.name.value === "populatedBy"); const selectableDirective = directives.find((x) => x.name.value === "selectable"); const settableDirective = directives.find((x) => x.name.value === "settable"); const filterableDirective = directives.find((x) => x.name.value === "filterable"); const fieldInterface = definitionCollection.interfaceTypes.get(typeMeta.name); const fieldUnion = definitionCollection.unionTypes.get(typeMeta.name); const fieldScalar = definitionCollection.scalarTypes.get(typeMeta.name); const fieldEnum = definitionCollection.enumTypes.get(typeMeta.name); const fieldObject = definitionCollection.objectTypes.get(typeMeta.name); const fieldTemporal = constants_1.TEMPORAL_SCALAR_TYPES.includes(typeMeta.name); const fieldPoint = constants_1.SPATIAL_TYPES.includes(typeMeta.name); const selectableOptions = parseSelectableDirective(selectableDirective); const settableOptions = parseSettableDirective(settableDirective); const filterableOptions = parseFilterableDirective(filterableDirective); const baseField = { fieldName: field.name.value, dbPropertyName: field.name.value, typeMeta, selectableOptions, settableOptions, filterableOptions, otherDirectives: (directives || []).filter((x) => ![ "relationship", "cypher", "id", "authorization", "authentication", "customResolver", "default", "coalesce", "timestamp", "alias", "callback", "populatedBy", "jwtClaim", "selectable", "settable", "subscriptionsAuthorization", "filterable", "relayId", ].includes(x.name.value)), arguments: [...(field.arguments || [])], description: field.description?.value, }; if (aliasDirective) { const aliasMeta = (0, get_alias_meta_1.default)(aliasDirective); if (aliasMeta) { baseField.dbPropertyName = aliasMeta.property; baseField.dbPropertyNameUnescaped = aliasMeta.propertyUnescaped; } } if (relationshipMeta) { const relationField = { ...baseField, ...relationshipMeta, inherited: false, }; if (fieldUnion) { const nodes = []; fieldUnion.types?.forEach((type) => { nodes.push(type.name.value); }); const unionField = { ...baseField, nodes, }; relationField.union = unionField; } if (fieldInterface) { const implementations = objects .filter((n) => n.interfaces?.some((i) => i.name.value === fieldInterface.name.value)) .map((n) => n.name.value); relationField.interface = { ...baseField, implementations, }; } // TODO: This will be brittle if more than one interface let connectionPrefix = obj.name.value; if (obj.interfaces && obj.interfaces.length) { const firstInterface = obj.interfaces[0]; if (!firstInterface) { throw new Error("Cannot get interface in getObjFieldMeta"); } const inter = interfaces.find((i) => i.name.value === firstInterface.name.value); if (inter.fields?.some((f) => f.name.value === baseField.fieldName)) { connectionPrefix = firstInterface.name.value; relationField.inherited = true; } } relationField.connectionPrefix = connectionPrefix; const connectionTypeName = `${connectionPrefix}${(0, upper_first_1.upperFirst)(`${baseField.fieldName}Connection`)}`; const relationshipTypeName = `${connectionPrefix}${(0, upper_first_1.upperFirst)(`${baseField.fieldName}Relationship`)}`; const connectionField = { fieldName: `${baseField.fieldName}Connection`, relationshipTypeName, selectableOptions, settableOptions, filterableOptions, typeMeta: { name: connectionTypeName, required: true, pretty: `${connectionTypeName}!`, input: { where: { type: `${connectionTypeName}Where`, pretty: `${connectionTypeName}Where`, }, create: { type: "", pretty: "", }, update: { type: "", pretty: "", }, }, }, otherDirectives: baseField.otherDirectives, arguments: [...(field.arguments || [])], description: field.description?.value, relationship: relationField, }; res.relationFields.push(relationField); res.connectionFields.push(connectionField); } else if (cypherMeta) { const cypherField = { ...baseField, ...cypherMeta, isEnum: !!fieldEnum, isScalar: !!fieldScalar || constants_1.SCALAR_TYPES.includes(typeMeta.name), }; res.cypherFields.push(cypherField); } else if (customResolverMeta) { res.customResolverFields.push({ ...baseField, ...customResolverMeta }); } else if (fieldScalar) { const scalarField = { ...baseField, }; res.scalarFields.push(scalarField); } else if (fieldEnum) { const enumField = { kind: "Enum", ...baseField, }; if (defaultDirective) { const defaultValue = defaultDirective.arguments?.find((a) => a.name.value === "value")?.value; if (enumField.typeMeta.array) { enumField.defaultValue = defaultValue.values.map((v) => { return v.value; }); } else { enumField.defaultValue = defaultValue.value; } } if (coalesceDirective) { const coalesceValue = coalesceDirective.arguments?.find((a) => a.name.value === "value")?.value; if (enumField.typeMeta.array) { enumField.coalesceValue = coalesceValue.values.map((v) => { return v.value; }); } else { // TODO: avoid duplication with primitives enumField.coalesceValue = `"${coalesceValue.value}"`; } } res.enumFields.push(enumField); } else if (fieldUnion) { const unionField = { ...baseField, }; res.unionFields.push(unionField); } else if (fieldInterface) { res.interfaceFields.push({ ...baseField, }); } else if (fieldObject) { const objectField = { ...baseField, }; res.objectFields.push(objectField); } else { if (fieldTemporal) { const temporalField = { ...baseField, }; if (populatedByDirective) { const callback = (0, get_populated_by_meta_1.getPopulatedByMeta)(populatedByDirective, callbacks); temporalField.callback = callback; } if (timestampDirective) { const operations = timestampDirective?.arguments?.find((x) => x.name.value === "operations") ?.value; const timestamps = operations ? operations?.values.map((x) => (0, parse_value_node_1.parseValueNode)(x)) : ["CREATE", "UPDATE"]; temporalField.timestamps = timestamps; } if (defaultDirective) { const value = defaultDirective.arguments?.find((a) => a.name.value === "value")?.value; temporalField.defaultValue = value.value; } res.temporalFields.push(temporalField); } else if (fieldPoint) { const pointField = { ...baseField, }; res.pointFields.push(pointField); } else { const primitiveField = { ...baseField, }; if (populatedByDirective) { const callback = (0, get_populated_by_meta_1.getPopulatedByMeta)(populatedByDirective, callbacks); primitiveField.callback = callback; } if (idDirective) { // TODO: remove the following as the argument "autogenerate" does not exists anymore const autogenerate = idDirective.arguments?.find((a) => a.name.value === "autogenerate"); if (!autogenerate || autogenerate.value.value) { primitiveField.autogenerate = true; } } if (relayIdDirective) { primitiveField.isGlobalIdField = true; } if (defaultDirective) { const value = defaultDirective.arguments?.find((a) => a.name.value === "value")?.value; const typeError = `Default value for ${obj.name.value}.${primitiveField.fieldName} does not have matching type ${primitiveField.typeMeta.name}`; switch (baseField.typeMeta.name) { case "ID": case "String": if (value?.kind !== graphql_1.Kind.STRING) { throw new Error(typeError); } primitiveField.defaultValue = value.value; break; case "Boolean": if (value?.kind !== graphql_1.Kind.BOOLEAN) { throw new Error(typeError); } primitiveField.defaultValue = value.value; break; case "Int": if (value?.kind !== graphql_1.Kind.INT) { throw new Error(typeError); } primitiveField.defaultValue = parseInt(value.value, 10); break; case "BigInt": if (value?.kind !== graphql_1.Kind.INT && value?.kind !== graphql_1.Kind.STRING) { throw new Error(typeError); } primitiveField.defaultValue = parseInt(value.value, 10); break; case "Float": if (value?.kind !== graphql_1.Kind.FLOAT && value?.kind !== graphql_1.Kind.INT) { throw new Error(typeError); } primitiveField.defaultValue = parseFloat(value.value); break; default: throw new Error("@default directive can only be used on fields of type Int, Float, String, Boolean, ID, BigInt, DateTime, Date, Time, LocalDateTime or LocalTime."); } } if (coalesceDirective) { const value = coalesceDirective.arguments?.find((a) => a.name.value === "value")?.value; const typeError = `coalesce() value for ${obj.name.value}.${primitiveField.fieldName} does not have matching type ${primitiveField.typeMeta.name}`; switch (baseField.typeMeta.name) { case "ID": case "String": if (value?.kind !== graphql_1.Kind.STRING) { throw new Error(typeError); } primitiveField.coalesceValue = `"${value.value}"`; break; case "Boolean": if (value?.kind !== graphql_1.Kind.BOOLEAN) { throw new Error(typeError); } primitiveField.coalesceValue = value.value; break; case "Int": if (value?.kind !== graphql_1.Kind.INT) { throw new Error(typeError); } primitiveField.coalesceValue = parseInt(value.value, 10); break; case "Float": if (value?.kind !== graphql_1.Kind.FLOAT) { throw new Error(typeError); } primitiveField.coalesceValue = parseFloat(value.value); break; default: throw new Error("@coalesce directive can only be used on types: Int | Float | String | Boolean | ID | DateTime"); } } res.primitiveFields.push(primitiveField); } } return res; }, { relationFields: [], connectionFields: [], primitiveFields: [], cypherFields: [], scalarFields: [], enumFields: [], unionFields: [], interfaceFields: [], objectFields: [], temporalFields: [], pointFields: [], customResolverFields: [], }); } function parseSelectableDirective(directive) { const defaultArguments = { onRead: true, onAggregate: true, }; const args = directive ? (0, parse_arguments_1.parseArgumentsFromUnknownDirective)(directive) : {}; return { onRead: args.onRead ?? defaultArguments.onRead, onAggregate: args.onAggregate ?? defaultArguments.onAggregate, }; } function parseSettableDirective(directive) { const defaultArguments = { onCreate: true, onUpdate: true, }; const args = directive ? (0, parse_arguments_1.parseArgumentsFromUnknownDirective)(directive) : {}; return { onCreate: args.onCreate ?? defaultArguments.onCreate, onUpdate: args.onUpdate ?? defaultArguments.onUpdate, }; } function parseFilterableDirective(directive) { const defaultArguments = { byValue: true, byAggregate: directive === undefined ? true : false, }; const args = directive ? (0, parse_arguments_1.parseArgumentsFromUnknownDirective)(directive) : {}; return { byValue: args.byValue ?? defaultArguments.byValue, byAggregate: args.byAggregate ?? defaultArguments.byAggregate, }; } //# sourceMappingURL=get-obj-field-meta.js.map