Skip to content

Commit

Permalink
Merge pull request neo4j#4036 from mjfwebb/schema-generation-refactor
Browse files Browse the repository at this point in the history
Schema generation refactor
  • Loading branch information
a-alle authored Sep 29, 2023
2 parents a419eea + 6822abc commit 1fd1efe
Show file tree
Hide file tree
Showing 243 changed files with 13,571 additions and 4,917 deletions.
10 changes: 10 additions & 0 deletions .changeset/great-toys-provide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@neo4j/graphql": minor
"@neo4j/graphql-ogm": minor
---

Schema generation logic improved

- allow operations on Interface relationships to Interfaces
- add descriptions to generated graphql types
- improve schema generation logic
40 changes: 23 additions & 17 deletions packages/graphql/src/classes/Neo4jGraphQL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,10 +339,8 @@ class Neo4jGraphQL {
}

private generateSchemaModel(document: DocumentNode): Neo4jGraphQLSchemaModel {
// This can be run several times but it will always be the same result,
// so we memoize the schemaModel.
if (!this.schemaModel) {
this.schemaModel = generateModel(document);
return generateModel(document);
}
return this.schemaModel;
}
Expand Down Expand Up @@ -372,13 +370,17 @@ class Neo4jGraphQL {
this.jwtFieldsMap = jwt.jwtFieldsMap;
}

this.generateSchemaModel(document);
this.schemaModel = this.generateSchemaModel(document);

const { nodes, relationships, typeDefs, resolvers } = makeAugmentedSchema(document, {
features: this.features,
generateSubscriptions: Boolean(this.features?.subscriptions),
userCustomResolvers: this.resolvers,
});
const { nodes, relationships, typeDefs, resolvers } = makeAugmentedSchema(
document,
{
features: this.features,
generateSubscriptions: Boolean(this.features?.subscriptions),
userCustomResolvers: this.resolvers,
},
this.schemaModel
);

if (this.validate) {
validateUserDefinition({ userDocument: document, augmentedDocument: typeDefs, jwt: jwt?.type });
Expand Down Expand Up @@ -433,14 +435,18 @@ class Neo4jGraphQL {
this.jwtFieldsMap = jwt.jwtFieldsMap;
}

const schemaModel = this.generateSchemaModel(document);
this.schemaModel = this.generateSchemaModel(document);

const { nodes, relationships, typeDefs, resolvers } = makeAugmentedSchema(document, {
features: this.features,
generateSubscriptions: Boolean(this.features?.subscriptions),
userCustomResolvers: this.resolvers,
subgraph,
});
const { nodes, relationships, typeDefs, resolvers } = makeAugmentedSchema(
document,
{
features: this.features,
generateSubscriptions: Boolean(this.features?.subscriptions),
userCustomResolvers: this.resolvers,
subgraph,
},
this.schemaModel
);

if (this.validate) {
validateUserDefinition({
Expand All @@ -456,7 +462,7 @@ class Neo4jGraphQL {
this._relationships = relationships;

// TODO: Move into makeAugmentedSchema, add resolvers alongside other resolvers
const referenceResolvers = subgraph.getReferenceResolvers(this._nodes, schemaModel);
const referenceResolvers = subgraph.getReferenceResolvers(this._nodes, this.schemaModel);

const schema = subgraph.buildSchema({
typeDefs,
Expand Down
7 changes: 3 additions & 4 deletions packages/graphql/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@ export const FIELD_DIRECTIVES = [
"alias",
"authentication",
"authorization",
"callback",
"coalesce",
"customResolver",
"cypher",
Expand All @@ -121,15 +120,13 @@ export const FIELD_DIRECTIVES = [
"id",
"jwtClaim",
"populatedBy",
"readonly",
"relationship",
"relayId",
"selectable",
"settable",
"subscriptionsAuthorization",
"timestamp",
"unique",
"writeonly",
] as const;

export type FieldDirective = (typeof FIELD_DIRECTIVES)[number];
Expand All @@ -144,7 +141,7 @@ export const OBJECT_DIRECTIVES = [
"node",
"plural",
"query",
"queryOptions",
"limit",
"shareable",
"subscription",
"subscriptionsAuthorization",
Expand All @@ -159,3 +156,5 @@ export type InterfaceDirective = (typeof INTERFACE_DIRECTIVES)[number];
export const DEPRECATED = "deprecated";

export const PROPAGATED_DIRECTIVES = ["shareable", DEPRECATED] as const;

export const PROPAGATED_DIRECTIVES_FROM_SCHEMA_TO_OBJECT = ["query", "mutation", "subscription"];
1 change: 1 addition & 0 deletions packages/graphql/src/graphql/enums/SortDirection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { GraphQLEnumType } from "graphql";

export const SortDirection = new GraphQLEnumType({
name: "SortDirection",
description: "An enum for sorting in either ascending or descending order.",
values: {
ASC: {
value: "ASC",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { CartesianPointInput } from "./CartesianPointInput";

export const CartesianPointDistance = new GraphQLInputObjectType({
name: "CartesianPointDistance",
description: "Input type for a cartesian point with a distance",
fields: {
point: {
type: new GraphQLNonNull(CartesianPointInput),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { GraphQLFloat, GraphQLInputObjectType, GraphQLNonNull } from "graphql";

export const CartesianPointInput = new GraphQLInputObjectType({
name: "CartesianPointInput",
description: "Input type for a cartesian point",
fields: {
x: {
type: new GraphQLNonNull(GraphQLFloat),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { PointInput } from "./PointInput";

export const PointDistance = new GraphQLInputObjectType({
name: "PointDistance",
description: "Input type for a point with a distance",
fields: {
point: {
type: new GraphQLNonNull(PointInput),
Expand Down
1 change: 1 addition & 0 deletions packages/graphql/src/graphql/input-objects/PointInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { GraphQLFloat, GraphQLInputObjectType, GraphQLNonNull } from "graphql";

export const PointInput = new GraphQLInputObjectType({
name: "PointInput",
description: "Input type for a point",
fields: {
longitude: {
type: new GraphQLNonNull(GraphQLFloat),
Expand Down
1 change: 1 addition & 0 deletions packages/graphql/src/graphql/input-objects/QueryOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { GraphQLInputObjectType, GraphQLInt } from "graphql";

export const QueryOptions = new GraphQLInputObjectType({
name: "QueryOptions",
description: "Input type for options that can be specified on a query operation.",
fields: {
offset: {
type: GraphQLInt,
Expand Down
2 changes: 2 additions & 0 deletions packages/graphql/src/graphql/objects/CartesianPoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { GraphQLFloat, GraphQLInt, GraphQLNonNull, GraphQLObjectType, GraphQLStr

export const CartesianPoint = new GraphQLObjectType({
name: "CartesianPoint",
description:
"A point in a two- or three-dimensional Cartesian coordinate system or in a three-dimensional cylindrical coordinate system. For more information, see https://neo4j.com/docs/graphql/4/type-definitions/types/spatial/#cartesian-point",
fields: {
x: {
type: new GraphQLNonNull(GraphQLFloat),
Expand Down
1 change: 1 addition & 0 deletions packages/graphql/src/graphql/objects/CreateInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { GraphQLInt, GraphQLNonNull, GraphQLObjectType, GraphQLString } from "gr

export const CreateInfo = new GraphQLObjectType({
name: "CreateInfo",
description: "Information about the number of nodes and relationships created during a create mutation",
fields: {
bookmark: {
type: GraphQLString,
Expand Down
1 change: 1 addition & 0 deletions packages/graphql/src/graphql/objects/DeleteInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { GraphQLInt, GraphQLNonNull, GraphQLObjectType, GraphQLString } from "gr

export const DeleteInfo = new GraphQLObjectType({
name: "DeleteInfo",
description: "Information about the number of nodes and relationships deleted during a delete mutation",
fields: {
bookmark: {
type: GraphQLString,
Expand Down
2 changes: 2 additions & 0 deletions packages/graphql/src/graphql/objects/Point.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { GraphQLFloat, GraphQLInt, GraphQLNonNull, GraphQLObjectType, GraphQLStr

export const Point = new GraphQLObjectType({
name: "Point",
description:
"A point in a coordinate system. For more information, see https://neo4j.com/docs/graphql/4/type-definitions/types/spatial/#point",
fields: {
longitude: {
type: new GraphQLNonNull(GraphQLFloat),
Expand Down
2 changes: 2 additions & 0 deletions packages/graphql/src/graphql/objects/UpdateInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { GraphQLInt, GraphQLNonNull, GraphQLObjectType, GraphQLString } from "gr

export const UpdateInfo = new GraphQLObjectType({
name: "UpdateInfo",
description:
"Information about the number of nodes and relationships created and deleted during an update mutation",
fields: {
bookmark: {
type: GraphQLString,
Expand Down
26 changes: 13 additions & 13 deletions packages/graphql/src/schema-model/Operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,43 +21,43 @@ import { Neo4jGraphQLSchemaValidationError } from "../classes";

import type { Annotation, Annotations } from "./annotation/Annotation";
import { annotationToKey } from "./annotation/Annotation";
import type { Field } from "./attribute/Field";
import type { Attribute } from "./attribute/Attribute";

export class Operation {
public readonly name: string;
// Currently only includes custom Cypher fields
public readonly fields: Map<string, Field> = new Map();
// only includes custom Cypher fields
public readonly attributes: Map<string, Attribute> = new Map();
public readonly annotations: Partial<Annotations> = {};

constructor({
name,
fields = [],
attributes = [],
annotations = [],
}: {
name: string;
fields?: Field[];
attributes?: Attribute[];
annotations?: Annotation[];
}) {
this.name = name;

for (const field of fields) {
this.addFields(field);
for (const attribute of attributes) {
this.addAttribute(attribute);
}

for (const annotation of annotations) {
this.addAnnotation(annotation);
}
}

public findFields(name: string): Field | undefined {
return this.fields.get(name);
public findAttribute(name: string): Attribute | undefined {
return this.attributes.get(name);
}

private addFields(field: Field): void {
if (this.fields.has(field.name)) {
throw new Neo4jGraphQLSchemaValidationError(`Field ${field.name} already exists in ${this.name}`);
private addAttribute(attribute: Attribute): void {
if (this.attributes.has(attribute.name)) {
throw new Neo4jGraphQLSchemaValidationError(`Attribute ${attribute.name} already exists in ${this.name}`);
}
this.fields.set(field.name, field);
this.attributes.set(attribute.name, attribute);
}

private addAnnotation(annotation: Annotation): void {
Expand Down
46 changes: 46 additions & 0 deletions packages/graphql/src/schema-model/OperationAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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.
*/

import type { Annotations } from "./annotation/Annotation";
import type { Attribute } from "./attribute/Attribute";
import { AttributeAdapter } from "./attribute/model-adapters/AttributeAdapter";
import type { Operation } from "./Operation";

export class OperationAdapter {
public readonly name: string;
public readonly attributes: Map<string, AttributeAdapter> = new Map();
public readonly annotations: Partial<Annotations> = {};

constructor(entity: Operation) {
this.name = entity.name;
this.initAttributes(entity.attributes);
this.annotations = entity.annotations;
}

private initAttributes(attributes: Map<string, Attribute>) {
for (const [attributeName, attribute] of attributes.entries()) {
const attributeAdapter = new AttributeAdapter(attribute);
this.attributes.set(attributeName, attributeAdapter);
}
}

public get objectFields(): AttributeAdapter[] {
return Array.from(this.attributes.values()).filter((attribute) => attribute.isObjectField());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
* limitations under the License.
*/

import type { MutationOperations } from "../../graphql/directives/mutation";

export class MutationAnnotation {
public readonly operations: string[];
public readonly operations: Set<MutationOperations>;

constructor({ operations }: { operations: string[] }) {
constructor({ operations }: { operations: Set<MutationOperations> }) {
this.operations = operations;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
* limitations under the License.
*/

import type { SubscriptionEvent } from "../../graphql/directives/subscription";

export class SubscriptionAnnotation {
public readonly events: string[];
public readonly events: Set<SubscriptionEvent>;

constructor({ events }: { events: string[] }) {
constructor({ events }: { events: Set<SubscriptionEvent> }) {
this.events = events;
}
}
11 changes: 11 additions & 0 deletions packages/graphql/src/schema-model/attribute/AttributeType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,16 @@ export class InterfaceType {
}
}

export class InputType {
public readonly name: string;
public readonly isRequired: boolean;

constructor(name: string, isRequired: boolean) {
this.name = name;
this.isRequired = isRequired;
}
}

export class UnknownType {
public readonly name: string;
public readonly isRequired: boolean;
Expand All @@ -155,4 +165,5 @@ export type AttributeType =
| EnumType
| UnionType
| InterfaceType
| InputType
| UnknownType;
Loading

0 comments on commit 1fd1efe

Please sign in to comment.