Skip to content

Commit 179c8c8

Browse files
MacondoExpressmjfwebbangrykoala
authored
Schema model refactoring (#3689)
* Add directive annotations (#3615) * feat: add alias directive annotation * feat: add queryOptions directive annotation * feat: parse Kind.INT and Kind.Float into JS numbers * feat: add default directive annotation * feat: add coalesce directive annotation * feat: add customResolver annotation * feat: add ID directive annotation * refactor: use makeDirectiveNode in key-annotation tests * feat: add mutation directive annotation * feat: add plural directive annotation * feat: add filterable directive annotation * feat: add fulltext directive annotation * feat: add node directive annotation * feat: add populatedBy directive annotation * feat: add query directive annotation * feat: add private directive annotation * feat: add relationshipProperties annotation * feat: add selectable directive annotation * feat: add settable directive annotation * feat: add timestamp directive annotation * feat: add unique directive annotation * feat: add subscription directive annotation * feat: add jwt-claim directive annotation * feat: add jwt-payload directive annotation * feat: add directives to Annotation --------- Co-authored-by: MacondoExpress <[email protected]> * Schema model re-design (#3596) * initial refactor of the schema model * add unit tests for schema model Attributes * include initial design for the GraphQL models * add Attribute tests, improve Attribute models * merge schema model changes with authorization changes * fix relationship-field parsing on schema model * organise parser folder * Add missing GraphQL models to the Schema Models * change Schema Model test to showcase how to dynamically get EntityModels from relationships * improve coverage on the Attribute Models * add more tests to the ConcreteEntityModel * Apply suggestions from code review Co-authored-by: angrykoala <[email protected]> * lint fixes * rename graphql-models to model-adapters * remove @relationshipProperties annotation as it is implicit within the Relationship class * remove code duplicity around leadingUnderscores utility and test it * parseArguments of directive using default values, add Schema Model tests for annotations and relationships * remove not necessary node directive * polishing * add license header * Update packages/graphql/src/schema-model/parser/parse-arguments.ts * add graphqlDefaultValue to Attribute * remove the graphqlDefault logic * add comment on the PluralAnnotation --------- Co-authored-by: Michael Webb <[email protected]> Co-authored-by: angrykoala <[email protected]>
1 parent b2c1fa0 commit 179c8c8

File tree

109 files changed

+5230
-499
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+5230
-499
lines changed

packages/graphql/src/classes/Node.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import { GraphElement } from "./GraphElement";
4444
import type { NodeDirective } from "./NodeDirective";
4545
import type { QueryOptionsDirective } from "./QueryOptionsDirective";
4646
import type { SchemaConfiguration } from "../schema/schema-configuration";
47+
import { leadingUnderscores } from "../utils/leading-underscore";
4748

4849
export interface NodeConstructor extends GraphElementConstructor {
4950
name: string;
@@ -169,8 +170,8 @@ class Node extends GraphElement {
169170
...this.temporalFields,
170171
...this.enumFields,
171172
...this.objectFields,
172-
...this.scalarFields,
173-
...this.primitiveFields,
173+
...this.scalarFields, // these are just custom scalars
174+
...this.primitiveFields, // these are instead built-in scalars
174175
...this.interfaceFields,
175176
...this.objectFields,
176177
...this.unionFields,
@@ -179,6 +180,7 @@ class Node extends GraphElement {
179180
}
180181

181182
/** Fields you can apply auth allow and bind to */
183+
// Maybe we can remove this as they may not be used anymore in the new auth system
182184
public get authableFields(): AuthableField[] {
183185
return [
184186
...this.primitiveFields,
@@ -233,6 +235,7 @@ class Node extends GraphElement {
233235
};
234236
}
235237

238+
236239
public get fulltextTypeNames(): FulltextTypeNames {
237240
return {
238241
result: `${this.pascalCaseSingular}FulltextResult`,
@@ -318,20 +321,14 @@ class Node extends GraphElement {
318321
private generateSingular(): string {
319322
const singular = camelcase(this.name);
320323

321-
return `${this.leadingUnderscores(this.name)}${singular}`;
324+
return `${leadingUnderscores(this.name)}${singular}`;
322325
}
323326

324327
private generatePlural(inputPlural: string | undefined): string {
325328
const name = inputPlural || this.plural || this.name;
326329
const plural = inputPlural || this.plural ? camelcase(name) : pluralize(camelcase(name));
327330

328-
return `${this.leadingUnderscores(name)}${plural}`;
329-
}
330-
331-
private leadingUnderscores(name: string): string {
332-
const re = /^(_+).+/;
333-
const match = re.exec(name);
334-
return match?.[1] || "";
331+
return `${leadingUnderscores(name)}${plural}`;
335332
}
336333
}
337334

packages/graphql/src/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export const NODE_OR_EDGE_KEYS = ["node", "edge"];
7272
export const LOGICAL_OPERATORS = ["AND", "OR", "NOT"] as const;
7373

7474
// aggregation
75-
export const AGGREGATION_COMPARISON_OPERATORS = ["EQUAL", "GT", "GTE", "LT", "LTE"];
75+
export const AGGREGATION_COMPARISON_OPERATORS = ["EQUAL", "GT", "GTE", "LT", "LTE"] as const;
7676
export const AGGREGATION_AGGREGATE_COUNT_OPERATORS = ["count", "count_LT", "count_LTE", "count_GT", "count_GTE"];
7777

7878
export const WHERE_AGGREGATION_TYPES = [

packages/graphql/src/graphql/directives/arguments/enums/PopulatedByOperation.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const PopulatedByOperationEnum = new GraphQLEnumType({
2323
name: "PopulatedByOperation",
2424
description: "*For use in the @populatedBy directive only*",
2525
values: {
26-
CREATE: {},
27-
UPDATE: {},
26+
CREATE: { value: "CREATE" },
27+
UPDATE: { value: "UPDATE"},
2828
},
2929
});

packages/graphql/src/schema-model/Neo4jGraphQLSchemaModel.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import { annotationToKey } from "./annotation/Annotation";
2424
import { CompositeEntity } from "./entity/CompositeEntity";
2525
import { ConcreteEntity } from "./entity/ConcreteEntity";
2626
import type { Entity } from "./entity/Entity";
27+
import { ConcreteEntityAdapter } from "./entity/model-adapters/ConcreteEntityAdapter";
28+
2729
export type Operations = {
2830
Query?: Operation;
2931
Mutation?: Operation;
@@ -52,6 +54,7 @@ export class Neo4jGraphQLSchemaModel {
5254
acc.set(entity.name, entity);
5355
return acc;
5456
}, new Map<string, Entity>());
57+
5558
this.concreteEntities = concreteEntities;
5659
this.compositeEntities = compositeEntities;
5760
this.operations = operations;
@@ -65,6 +68,11 @@ export class Neo4jGraphQLSchemaModel {
6568
return this.entities.get(name);
6669
}
6770

71+
public getConcreteEntityAdapter(name: string): ConcreteEntityAdapter | undefined {
72+
const concreteEntity = this.concreteEntities.find((entity) => entity.name === name);
73+
return concreteEntity ? new ConcreteEntityAdapter(concreteEntity) : undefined;
74+
}
75+
6876
public getEntitiesByLabels(labels: string[]): ConcreteEntity[] {
6977
return this.concreteEntities.filter((entity) => entity.matchLabels(labels));
7078
}

packages/graphql/src/schema-model/annotation/Annotation.ts

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,81 @@
1919

2020
import { AuthenticationAnnotation } from "./AuthenticationAnnotation";
2121
import { AuthorizationAnnotation } from "./AuthorizationAnnotation";
22+
import { CoalesceAnnotation } from "./CoalesceAnnotation";
23+
import { CustomResolverAnnotation } from "./CustomResolverAnnotation";
2224
import { CypherAnnotation } from "./CypherAnnotation";
25+
import { DefaultAnnotation } from "./DefaultAnnotation";
26+
import { FilterableAnnotation } from "./FilterableAnnotation";
27+
import { FullTextAnnotation } from "./FullTextAnnotation";
28+
import { IDAnnotation } from "./IDAnnotation";
29+
import { JWTClaimAnnotation } from "./JWTClaimAnnotation";
30+
import { JWTPayloadAnnotation } from "./JWTPayloadAnnotation";
2331
import { KeyAnnotation } from "./KeyAnnotation";
32+
import { MutationAnnotation } from "./MutationAnnotation";
33+
import { PluralAnnotation } from "./PluralAnnotation";
34+
import { PopulatedByAnnotation } from "./PopulatedByAnnotation";
35+
import { PrivateAnnotation } from "./PrivateAnnotation";
36+
import { QueryAnnotation } from "./QueryAnnotation";
37+
import { QueryOptionsAnnotation } from "./QueryOptionsAnnotation";
38+
import { SelectableAnnotation } from "./SelectableAnnotation";
39+
import { SettableAnnotation } from "./SettableAnnotation";
40+
import { SubscriptionAnnotation } from "./SubscriptionAnnotation";
2441
import { SubscriptionsAuthorizationAnnotation } from "./SubscriptionsAuthorizationAnnotation";
42+
import { TimestampAnnotation } from "./TimestampAnnotation";
43+
import { UniqueAnnotation } from "./UniqueAnnotation";
2544

2645
export type Annotation =
2746
| CypherAnnotation
2847
| AuthorizationAnnotation
2948
| AuthenticationAnnotation
3049
| KeyAnnotation
31-
| SubscriptionsAuthorizationAnnotation;
50+
| SubscriptionsAuthorizationAnnotation
51+
| QueryOptionsAnnotation
52+
| DefaultAnnotation
53+
| CoalesceAnnotation
54+
| CustomResolverAnnotation
55+
| IDAnnotation
56+
| MutationAnnotation
57+
| PluralAnnotation
58+
| FilterableAnnotation
59+
| FullTextAnnotation
60+
| PopulatedByAnnotation
61+
| QueryAnnotation
62+
| PrivateAnnotation
63+
| SelectableAnnotation
64+
| SettableAnnotation
65+
| TimestampAnnotation
66+
| UniqueAnnotation
67+
| SubscriptionAnnotation
68+
| JWTClaimAnnotation
69+
| JWTPayloadAnnotation;
70+
3271

3372
export enum AnnotationsKey {
34-
cypher = "cypher",
35-
authorization = "authorization",
3673
authentication = "authentication",
74+
authorization = "authorization",
75+
coalesce = "coalesce",
76+
customResolver = "customResolver",
77+
cypher = "cypher",
78+
default = "default",
79+
filterable = "filterable",
80+
fulltext = "fulltext",
81+
id = "id",
82+
jwtClaim = "jwtClaim",
83+
jwtPayload = "jwtPayload",
3784
key = "key",
85+
mutation = "mutation",
86+
plural = "plural",
87+
populatedBy = "populatedBy",
88+
private = "private",
89+
query = "query",
90+
queryOptions = "queryOptions",
91+
selectable = "selectable",
92+
settable = "settable",
93+
subscription = "subscription",
3894
subscriptionsAuthorization = "subscriptionsAuthorization",
95+
timestamp = "timestamp",
96+
unique = "unique",
3997
}
4098

4199
export type Annotations = {
@@ -44,6 +102,25 @@ export type Annotations = {
44102
[AnnotationsKey.authentication]: AuthenticationAnnotation;
45103
[AnnotationsKey.key]: KeyAnnotation;
46104
[AnnotationsKey.subscriptionsAuthorization]: SubscriptionsAuthorizationAnnotation;
105+
[AnnotationsKey.queryOptions]: QueryOptionsAnnotation;
106+
[AnnotationsKey.default]: DefaultAnnotation;
107+
[AnnotationsKey.coalesce]: CoalesceAnnotation;
108+
[AnnotationsKey.customResolver]: CustomResolverAnnotation;
109+
[AnnotationsKey.id]: IDAnnotation;
110+
[AnnotationsKey.mutation]: MutationAnnotation;
111+
[AnnotationsKey.plural]: PluralAnnotation;
112+
[AnnotationsKey.filterable]: FilterableAnnotation;
113+
[AnnotationsKey.fulltext]: FullTextAnnotation;
114+
[AnnotationsKey.populatedBy]: PopulatedByAnnotation;
115+
[AnnotationsKey.query]: QueryAnnotation;
116+
[AnnotationsKey.private]: PrivateAnnotation;
117+
[AnnotationsKey.selectable]: SelectableAnnotation;
118+
[AnnotationsKey.settable]: SettableAnnotation;
119+
[AnnotationsKey.timestamp]: TimestampAnnotation;
120+
[AnnotationsKey.unique]: UniqueAnnotation;
121+
[AnnotationsKey.subscription]: SubscriptionAnnotation;
122+
[AnnotationsKey.jwtClaim]: JWTClaimAnnotation;
123+
[AnnotationsKey.jwtPayload]: JWTPayloadAnnotation;
47124
};
48125

49126
export function annotationToKey(ann: Annotation): keyof Annotations {
@@ -52,5 +129,24 @@ export function annotationToKey(ann: Annotation): keyof Annotations {
52129
if (ann instanceof AuthenticationAnnotation) return AnnotationsKey.authentication;
53130
if (ann instanceof KeyAnnotation) return AnnotationsKey.key;
54131
if (ann instanceof SubscriptionsAuthorizationAnnotation) return AnnotationsKey.subscriptionsAuthorization;
132+
if (ann instanceof QueryOptionsAnnotation) return AnnotationsKey.queryOptions;
133+
if (ann instanceof DefaultAnnotation) return AnnotationsKey.default;
134+
if (ann instanceof CoalesceAnnotation) return AnnotationsKey.coalesce;
135+
if (ann instanceof CustomResolverAnnotation) return AnnotationsKey.customResolver;
136+
if (ann instanceof IDAnnotation) return AnnotationsKey.id;
137+
if (ann instanceof MutationAnnotation) return AnnotationsKey.mutation;
138+
if (ann instanceof PluralAnnotation) return AnnotationsKey.plural;
139+
if (ann instanceof FilterableAnnotation) return AnnotationsKey.filterable;
140+
if (ann instanceof FullTextAnnotation) return AnnotationsKey.fulltext;
141+
if (ann instanceof PopulatedByAnnotation) return AnnotationsKey.populatedBy;
142+
if (ann instanceof QueryAnnotation) return AnnotationsKey.query;
143+
if (ann instanceof PrivateAnnotation) return AnnotationsKey.private;
144+
if (ann instanceof SelectableAnnotation) return AnnotationsKey.selectable;
145+
if (ann instanceof SettableAnnotation) return AnnotationsKey.settable;
146+
if (ann instanceof TimestampAnnotation) return AnnotationsKey.timestamp;
147+
if (ann instanceof UniqueAnnotation) return AnnotationsKey.unique;
148+
if (ann instanceof SubscriptionAnnotation) return AnnotationsKey.subscription;
149+
if (ann instanceof JWTClaimAnnotation) return AnnotationsKey.jwtClaim;
150+
if (ann instanceof JWTPayloadAnnotation) return AnnotationsKey.jwtPayload;
55151
throw new Error("annotation not known");
56152
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
export type CoalesceAnnotationValue = string | number | boolean;
21+
22+
export class CoalesceAnnotation {
23+
public readonly value: CoalesceAnnotationValue;
24+
25+
constructor({ value }: { value: CoalesceAnnotationValue }) {
26+
this.value = value;
27+
}
28+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
export class CustomResolverAnnotation {
21+
public readonly requires: string;
22+
23+
constructor({ requires }: { requires: string }) {
24+
this.requires = requires;
25+
}
26+
}

packages/graphql/src/schema-model/annotation/CypherAnnotation.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@
1717
* limitations under the License.
1818
*/
1919

20-
2120
export class CypherAnnotation {
2221
public statement: string;
22+
public columnName: string;
2323

24-
constructor({ statement }: { statement: string }) {
24+
constructor({ statement, columnName }: { statement: string, columnName: string }) {
2525
this.statement = statement;
26+
this.columnName = columnName;
2627
}
2728
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
export type DefaultAnnotationValue = string | number | boolean;
21+
22+
export class DefaultAnnotation {
23+
public readonly value: DefaultAnnotationValue;
24+
25+
constructor({ value }: { value: DefaultAnnotationValue }) {
26+
this.value = value;
27+
}
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
export class FilterableAnnotation {
21+
public readonly byValue: boolean;
22+
public readonly byAggregate: boolean;
23+
24+
constructor({ byValue, byAggregate }: { byValue: boolean; byAggregate: boolean }) {
25+
this.byValue = byValue;
26+
this.byAggregate = byAggregate;
27+
}
28+
}

0 commit comments

Comments
 (0)