Skip to content

Commit dd7afb9

Browse files
authored
Breaking changes to the @id directive (#3823)
* Break `@id` `global` argument out into `@relayId` * Remove arguments from `@id` * Add changeset * Address first PR comments * Switch back to default name for constraints * Fix directive description * Remove automatic constraint for `@id` * Fix typo * Fix tests
1 parent 5db64da commit dd7afb9

File tree

117 files changed

+338
-601
lines changed

Some content is hidden

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

117 files changed

+338
-601
lines changed

.changeset/purple-hairs-glow.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
"@neo4j/graphql": major
3+
---
4+
5+
The `@id` directive has had a number of breaking changes.
6+
7+
The `unique` argument has been removed. In an effort to simplify directives, `@id` will now only only be responsible for the autogeneration of UUID values.
8+
If you would like the property to also be backed by a unique node property constraint, use the `@unique` directive alongside `@id`.
9+
10+
The `autogenerate` argument has been removed. With this value set to `false` and the above removal of constraint management, this would make the directive a no-op.
11+
12+
The `global` argument has been removed. This quite key feature of specifying the globally unique identifier for Relay was hidden away inside the `@id` directive. This functionality has been moved into its own directive, `@relayId`, which is used with no arguments. The use of the `@relayId` directive also implies that the field will be backed by a unique node property constraint.
13+
14+
Note, if using the `@id` and `@relayId` directive together on the same field, this will be an autogenerated ID compatible with Relay, and be backed by a single unique node property constraint. If you wish to give this constraint a name, use the `@unique` directive also with the `constraintName` argument.

packages/graphql/src/classes/Neo4jGraphQL.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import type Node from "./Node";
2525
import type Relationship from "./Relationship";
2626
import checkNeo4jCompat from "./utils/verify-database";
2727
import type { AssertIndexesAndConstraintsOptions } from "./utils/asserts-indexes-and-constraints";
28-
import assertIndexesAndConstraints from "./utils/asserts-indexes-and-constraints";
28+
import { assertIndexesAndConstraints } from "./utils/asserts-indexes-and-constraints";
2929
import { wrapQueryAndMutation } from "../schema/resolvers/composition/wrap-query-and-mutation";
3030
import type { WrapResolverArguments } from "../schema/resolvers/composition/wrap-query-and-mutation";
3131
import { defaultFieldResolver } from "../schema/resolvers/field/defaultField";

packages/graphql/src/classes/utils/asserts-indexes-and-constraints.ts

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,32 @@ export interface AssertIndexesAndConstraintsOptions {
2929
create?: boolean;
3030
}
3131

32+
export async function assertIndexesAndConstraints({
33+
driver,
34+
sessionConfig,
35+
nodes,
36+
options,
37+
}: {
38+
driver: Driver;
39+
sessionConfig?: Neo4jGraphQLSessionConfig;
40+
nodes: Node[];
41+
options?: AssertIndexesAndConstraintsOptions;
42+
}): Promise<void> {
43+
await driver.verifyConnectivity();
44+
45+
const session = driver.session(sessionConfig);
46+
47+
try {
48+
if (options?.create) {
49+
await createIndexesAndConstraints({ nodes, session });
50+
} else {
51+
await checkIndexesAndConstraints({ nodes, session });
52+
}
53+
} finally {
54+
await session.close();
55+
}
56+
}
57+
3258
async function createIndexesAndConstraints({ nodes, session }: { nodes: Node[]; session: Session }) {
3359
const constraintsToCreate = await getMissingConstraints({ nodes, session });
3460
const indexesToCreate: { indexName: string; label: string; properties: string[] }[] = [];
@@ -261,31 +287,3 @@ async function getMissingConstraints({
261287

262288
return missingConstraints;
263289
}
264-
265-
async function assertIndexesAndConstraints({
266-
driver,
267-
sessionConfig,
268-
nodes,
269-
options,
270-
}: {
271-
driver: Driver;
272-
sessionConfig?: Neo4jGraphQLSessionConfig;
273-
nodes: Node[];
274-
options?: AssertIndexesAndConstraintsOptions;
275-
}): Promise<void> {
276-
await driver.verifyConnectivity();
277-
278-
const session = driver.session(sessionConfig);
279-
280-
try {
281-
if (options?.create) {
282-
await createIndexesAndConstraints({ nodes, session });
283-
} else {
284-
await checkIndexesAndConstraints({ nodes, session });
285-
}
286-
} finally {
287-
await session.close();
288-
}
289-
}
290-
291-
export default assertIndexesAndConstraints;

packages/graphql/src/graphql/directives/id.ts

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

20-
import { DirectiveLocation, GraphQLBoolean, GraphQLDirective, GraphQLNonNull } from "graphql";
20+
import { DirectiveLocation, GraphQLDirective } from "graphql";
2121

2222
export const idDirective = new GraphQLDirective({
2323
name: "id",
24-
description:
25-
"Indicates that the field is an identifier for the object type. By default; autogenerated, and has a unique node property constraint in the database.",
24+
description: "Enables the autogeneration of UUID values for an ID field. The field becomes immutable.",
2625
locations: [DirectiveLocation.FIELD_DEFINITION],
27-
args: {
28-
autogenerate: {
29-
defaultValue: false,
30-
type: new GraphQLNonNull(GraphQLBoolean),
31-
},
32-
unique: {
33-
defaultValue: true,
34-
type: new GraphQLNonNull(GraphQLBoolean),
35-
},
36-
global: {
37-
description: "Opt-in to implementing the Node interface with a globally unique id",
38-
type: new GraphQLNonNull(GraphQLBoolean),
39-
defaultValue: false,
40-
},
41-
},
4226
});

packages/graphql/src/graphql/directives/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export { limitDirective } from "./limit";
3535
export { readonlyDirective } from "./readonly";
3636
export { relationshipPropertiesDirective } from "./relationship-properties";
3737
export { relationshipDirective } from "./relationship";
38+
export { relayIdDirective } from "./relay-id";
3839
export { timestampDirective } from "./timestamp";
3940
export { uniqueDirective } from "./unique";
4041
export { writeonlyDirective } from "./writeonly";

packages/graphql/src/schema-model/parser/annotations-parser/id-annotation.ts renamed to packages/graphql/src/graphql/directives/relay-id.ts

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

20-
import { type DirectiveNode } from "graphql";
21-
import { IDAnnotation } from "../../annotation/IDAnnotation";
22-
import { parseArguments } from "../parse-arguments";
23-
import { idDirective } from "../../../graphql/directives";
20+
import { DirectiveLocation, GraphQLDirective } from "graphql";
2421

25-
export function parseIDAnnotation(directive: DirectiveNode): IDAnnotation {
26-
const { autogenerate, unique, global } = parseArguments(idDirective, directive) as {
27-
autogenerate: boolean;
28-
unique: boolean;
29-
global: boolean;
30-
};
31-
32-
return new IDAnnotation({
33-
autogenerate,
34-
unique,
35-
global,
36-
});
37-
}
22+
export const relayIdDirective = new GraphQLDirective({
23+
name: "relayId",
24+
description:
25+
"Mark the field to be used as the global node identifier for Relay. This field will be backed by a unique node property constraint.",
26+
locations: [DirectiveLocation.FIELD_DEFINITION],
27+
});

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

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,4 @@
1717
* limitations under the License.
1818
*/
1919

20-
export class IDAnnotation {
21-
public readonly autogenerate: boolean;
22-
public readonly unique: boolean;
23-
public readonly global: boolean;
24-
25-
constructor({ autogenerate, unique, global }: { autogenerate: boolean; unique: boolean; global: boolean }) {
26-
this.autogenerate = autogenerate;
27-
this.unique = unique;
28-
this.global = global;
29-
}
30-
}
20+
export class IDAnnotation {}

packages/graphql/src/schema-model/parser/annotations-parser/id-annotation.test.ts

Lines changed: 0 additions & 62 deletions
This file was deleted.

packages/graphql/src/schema-model/parser/parse-annotation.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import { parseCoalesceAnnotation } from "./annotations-parser/coalesce-annotatio
2222
import { parseCypherAnnotation } from "./annotations-parser/cypher-annotation";
2323
import { parseCustomResolverAnnotation } from "./annotations-parser/custom-resolver-annotation";
2424
import { parseDefaultAnnotation } from "./annotations-parser/default-annotation";
25-
import { parseIDAnnotation } from "./annotations-parser/id-annotation";
2625
import { parseFilterableAnnotation } from "./annotations-parser/filterable-annotation";
2726
import { parseMutationAnnotation } from "./annotations-parser/mutation-annotation";
2827
import { parsePluralAnnotation } from "./annotations-parser/plural-annotation";
@@ -44,6 +43,7 @@ import { parseSubscriptionsAuthorizationAnnotation } from "./annotations-parser/
4443
import { filterTruthy } from "../../utils/utils";
4544
import type { Annotation } from "../annotation/Annotation";
4645
import { AnnotationsKey } from "../annotation/Annotation";
46+
import { IDAnnotation } from "../annotation/IDAnnotation";
4747

4848
export function parseAnnotations(directives: readonly DirectiveNode[]): Annotation[] {
4949
return filterTruthy(
@@ -66,7 +66,7 @@ export function parseAnnotations(directives: readonly DirectiveNode[]): Annotati
6666
case AnnotationsKey.fulltext:
6767
return parseFullTextAnnotation(directive);
6868
case AnnotationsKey.id:
69-
return parseIDAnnotation(directive);
69+
return new IDAnnotation();
7070
case AnnotationsKey.jwtClaim:
7171
return parseJWTClaimAnnotation(directive);
7272
case AnnotationsKey.jwtPayload:

packages/graphql/src/schema/check-directive-combinations.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ function checkDirectiveCombinations(directives: readonly DirectiveNode[] = []):
3030
customResolver: ["alias", "authentication", "authorization", "id", "readonly", "relationship", "writeonly"],
3131
cypher: [],
3232
default: [],
33-
id: ["cypher", "customResolver", "relationship", "timestamp", "unique"],
33+
id: ["cypher", "customResolver", "relationship", "timestamp"],
3434
populatedBy: ["id", "default", "relationship"],
3535
private: [],
3636
readonly: ["cypher", "customResolver"],
@@ -47,7 +47,7 @@ function checkDirectiveCombinations(directives: readonly DirectiveNode[] = []):
4747
"readonly",
4848
],
4949
timestamp: ["id", "unique"],
50-
unique: ["cypher", "id", "customResolver", "relationship", "timestamp"],
50+
unique: ["cypher", "customResolver", "relationship", "timestamp"],
5151
writeonly: ["cypher", "customResolver"],
5252
// OBJECT
5353
node: [],

0 commit comments

Comments
 (0)