From 48515183a49c6f7f45f81b670a4f0ca24bb952f9 Mon Sep 17 00:00:00 2001 From: MacondoExpress Date: Fri, 6 Sep 2024 12:12:39 +0100 Subject: [PATCH 1/9] use @node in typeDefs --- .../ROOT/pages/directives/autogeneration.adoc | 8 +- .../pages/directives/custom-directives.adoc | 2 +- .../ROOT/pages/directives/custom-logic.adoc | 38 +++--- .../pages/directives/database-mapping.adoc | 16 +-- .../directives/indexes-and-constraints.adoc | 16 +-- .../field-configuration.adoc | 44 +++---- .../global-configuration.adoc | 8 +- .../type-configuration.adoc | 18 +-- modules/ROOT/pages/driver-configuration.adoc | 16 +-- modules/ROOT/pages/getting-started/index.adoc | 8 +- .../ROOT/pages/getting-started/toolbox.adoc | 4 +- .../pages/integrations/apollo-federation.adoc | 8 +- .../integrations/relay-compatibility.adoc | 6 +- modules/ROOT/pages/mutations/create.adoc | 8 +- modules/ROOT/pages/mutations/delete.adoc | 4 +- modules/ROOT/pages/mutations/update.adoc | 16 +-- modules/ROOT/pages/ogm/directives.adoc | 4 +- modules/ROOT/pages/ogm/installation.adoc | 4 +- modules/ROOT/pages/ogm/reference.adoc | 8 +- modules/ROOT/pages/ogm/selection-set.adoc | 12 +- modules/ROOT/pages/ogm/subscriptions.adoc | 4 +- modules/ROOT/pages/ogm/type-generation.adoc | 2 +- .../queries-aggregations/aggregations.adoc | 4 +- .../pages/queries-aggregations/filtering.adoc | 123 ++++++++++++++++-- .../pagination/cursor-based.adoc | 4 +- .../pagination/offset-based.adoc | 2 +- .../pages/queries-aggregations/queries.adoc | 4 +- .../pages/queries-aggregations/sorting.adoc | 4 +- .../ROOT/pages/security/authentication.adoc | 43 +++++- .../ROOT/pages/security/authorization.adoc | 14 +- .../impersonation-and-user-switching.adoc | 8 +- modules/ROOT/pages/security/operations.adoc | 2 +- .../security/subscriptions-authorization.adoc | 6 +- modules/ROOT/pages/subscriptions/events.adoc | 72 +++++----- .../ROOT/pages/subscriptions/filtering.adoc | 16 +-- .../pages/subscriptions/getting-started.adoc | 4 +- modules/ROOT/pages/troubleshooting.adoc | 10 +- modules/ROOT/pages/types/interfaces.adoc | 6 +- modules/ROOT/pages/types/relationships.adoc | 24 ++-- modules/ROOT/pages/types/scalar.adoc | 12 +- modules/ROOT/pages/types/spatial.adoc | 8 +- modules/ROOT/pages/types/temporal.adoc | 12 +- modules/ROOT/pages/types/unions.adoc | 6 +- 43 files changed, 388 insertions(+), 250 deletions(-) diff --git a/modules/ROOT/pages/directives/autogeneration.adoc b/modules/ROOT/pages/directives/autogeneration.adoc index 5e0d9c76..7688f518 100644 --- a/modules/ROOT/pages/directives/autogeneration.adoc +++ b/modules/ROOT/pages/directives/autogeneration.adoc @@ -31,7 +31,7 @@ The following type definition specifies the `id` field as an autogenerated value [source, graphql, indent=0] ---- -type User { +type User @node { id: ID! @id username: String! } @@ -70,7 +70,7 @@ The following type definition has two individual fields to store the timestamps [source, graphql, indent=0] ---- -type User { +type User @node { createdAt: DateTime! @timestamp(operations: [CREATE]) updatedAt: DateTime! @timestamp(operations: [UPDATE]) } @@ -80,14 +80,14 @@ The following two equivalent type definitions have a single field storing the ev [source, graphql, indent=0] ---- -type User { +type User @node { lastModified: DateTime! @timestamp } ---- [source, graphql, indent=0] ---- -type User { +type User @node { lastModified: DateTime! @timestamp(operations: [CREATE, UPDATE]) } ---- diff --git a/modules/ROOT/pages/directives/custom-directives.adoc b/modules/ROOT/pages/directives/custom-directives.adoc index c4c0ae5a..aaf6bcad 100644 --- a/modules/ROOT/pages/directives/custom-directives.adoc +++ b/modules/ROOT/pages/directives/custom-directives.adoc @@ -57,7 +57,7 @@ const neoSchema = new Neo4jGraphQL({ typeDefs: [ upperDirectiveTypeDefs, `#graphql - type Movie { + type Movie @node { name: String @uppercase } `, diff --git a/modules/ROOT/pages/directives/custom-logic.adoc b/modules/ROOT/pages/directives/custom-logic.adoc index 38d1e56f..bd79b0ea 100644 --- a/modules/ROOT/pages/directives/custom-logic.adoc +++ b/modules/ROOT/pages/directives/custom-logic.adoc @@ -47,7 +47,7 @@ interface Auth { a| You can use the JWT in the request to return the value of the currently logged in User: [source, graphql, indent=0] ---- -type User { +type User @node { id: String } @@ -117,7 +117,7 @@ Both approaches are demonstrated here: [source, graphql, indent=0] ---- -type User { +type User @node { id } @@ -135,7 +135,7 @@ type Query { [source, graphql, indent=0] ---- -type User { +type User @node { id } @@ -185,13 +185,13 @@ In the following example, the field `similarMovies` is bound to the `Movie` type [source, graphql, indent=0] ---- -type Actor { +type Actor @node { actorId: ID! name: String movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) } -type Movie { +type Movie @node { movieId: ID! title: String description: String @@ -216,7 +216,7 @@ The following example demonstrates a query to return all of the actors in the da [source, graphql, indent=0] ---- -type Actor { +type Actor @node { actorId: ID! name: String } @@ -239,7 +239,7 @@ The following example demonstrates a mutation using a Cypher query to insert a s [source, graphql, indent=0] ---- -type Actor { +type Actor @node { actorId: ID! name: String } @@ -291,7 +291,7 @@ enum Status { ACTIVE INACTIVE } -type Movie { +type Movie @node { status: Status @coalesce(value: ACTIVE) } ---- @@ -345,7 +345,7 @@ Take, for instance, this schema: [source, javascript, indent=0] ---- const typeDefs = ` - type User { + type User @node { firstName: String! lastName: String! fullName: String! @customResolver(requires: "firstName lastName") @@ -397,13 +397,13 @@ Using a selection set string makes it possible to select fields from related typ [source, javascript, indent=0] ---- const typeDefs = ` - type Address { + type Address @node { houseNumber: Int! street: String! city: String! } - type User { + type User @node { id: ID! firstName: String! lastName: String! @@ -437,7 +437,7 @@ interface Publication { publicationYear: Int! } -type Author { +type Author @node { name: String! publications: [Publication!]! @relationship(type: "WROTE", direction: OUT) publicationsWithAuthor: [String!]! @@ -446,13 +446,13 @@ type Author { ) } -type Book implements Publication { +type Book implements Publication @node { title: String! publicationYear: Int! author: [Author!]! @relationship(type: "WROTE", direction: IN) } -type Journal implements Publication { +type Journal implements Publication @node { subject: String! publicationYear: Int! author: [Author!]! @relationship(type: "WROTE", direction: IN) @@ -468,7 +468,7 @@ interface Publication { publicationYear: Int! } -type Author { +type Author @node { name: String! publications: [Publication!]! @relationship(type: "WROTE", direction: OUT) publicationsWithAuthor: [String!]! @@ -477,13 +477,13 @@ type Author { ) } -type Book implements Publication { +type Book implements Publication @node { title: String! publicationYear: Int! author: [Author!]! @relationship(type: "WROTE", direction: IN) } -type Journal implements Publication { +type Journal implements Publication @node { subject: String! publicationYear: Int! author: [Author!]! @relationship(type: "WROTE", direction: IN) @@ -525,7 +525,7 @@ Type definitions: [source, graphql, indent=0] ---- -type Product { +type Product @node { name: String! slug: String! @populatedBy(callback: "slug", operations: [CREATE, UPDATE]) } @@ -561,7 +561,7 @@ For example, if you want a field `modifiedBy`: [source, graphql, indent=0] ---- -type Record { +type Record @node { content: String! modifiedBy: @populatedBy(callback: "modifiedBy", operations: [CREATE, UPDATE]) } diff --git a/modules/ROOT/pages/directives/database-mapping.adoc b/modules/ROOT/pages/directives/database-mapping.adoc index f0bf111a..df75f546 100644 --- a/modules/ROOT/pages/directives/database-mapping.adoc +++ b/modules/ROOT/pages/directives/database-mapping.adoc @@ -17,12 +17,12 @@ To add a second node type, "Actor", and connect the two together, you should do [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) } -type Actor { +type Actor @node { name: String movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) } @@ -51,12 +51,12 @@ For example, for the "ACTED_IN" relationship, add a property "roles": [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") } -type Actor { +type Actor @node { name: String movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedIn") } @@ -78,7 +78,7 @@ For example, to represent a node with the label "Movie" and a single property "t [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String } ---- @@ -227,7 +227,7 @@ For example: [source, graphql, indent=0] ---- -type User { +type User @node { id: ID! @id @alias(property: "dbId") username: String! } @@ -235,13 +235,13 @@ type User { [source, graphql, indent=0] ---- -type User { +type User @node { id: ID! @id username: String! @alias(property: "dbUserName") livesIn: [City!]! @relationship(direction: OUT, type: "LIVES_IN", properties: "UserLivesInProperties") } -type City { +type City @node { name: String } diff --git a/modules/ROOT/pages/directives/indexes-and-constraints.adoc b/modules/ROOT/pages/directives/indexes-and-constraints.adoc index 9cdbafd5..5e4f9748 100644 --- a/modules/ROOT/pages/directives/indexes-and-constraints.adoc +++ b/modules/ROOT/pages/directives/indexes-and-constraints.adoc @@ -29,7 +29,7 @@ In the following example, a unique constraint is asserted for the label `Colour` [source, graphql, indent=0] ---- -type Colour { +type Colour @node { hexadecimal: String! @unique } ---- @@ -38,7 +38,7 @@ In the next example, a unique constraint with name `unique_colour` is asserted f [source, graphql, indent=0] ---- -type Colour { +type Colour @node { hexadecimal: String! @unique(constraintName: "unique_colour") } ---- @@ -92,7 +92,7 @@ In this example, a `Fulltext` index called "ProductName", for the name `field`, [source, graphql, indent=0] ---- -type Product @fulltext(indexes: [{ indexName: "ProductName", fields: ["name"] }]) { +type Product @fulltext(indexes: [{ indexName: "ProductName", fields: ["name"] }]) @node { name: String! color: Color! @relationship(type: "OF_COLOR", direction: OUT) } @@ -191,7 +191,7 @@ Additionally, it is possible to define a custom query name as part of the `@full [source, graphql, indent=0] ---- -type Product @fulltext(indexes: [{ queryName: "CustomProductFulltextQuery", indexName: "ProductName", fields: ["name"] }]) { +type Product @fulltext(indexes: [{ queryName: "CustomProductFulltextQuery", indexName: "ProductName", fields: ["name"] }]) @node { name: String! color: Color! @relationship(type: "OF_COLOR", direction: OUT) } @@ -230,12 +230,12 @@ This creates two constraints, one for each field decorated with `@id` and `@uniq [source, javascript, indent=0] ---- const typeDefs = `#graphql - type Color { + type Color @node { id: ID! @id hexadecimal: String! @unique } - type Product @fulltext(indexes: [{ indexName: "ProductName", fields: ["name"] }]) { + type Product @fulltext(indexes: [{ indexName: "ProductName", fields: ["name"] }]) @node { name: String! color: Color! @relationship(type: "OF_COLOR", direction: OUT) } @@ -316,7 +316,7 @@ Perform a nearest neighbor search by passing a vector to find nodes with a vecto .Type definition [source, graphql] ---- -type Product @vector(indexes: [{ +type Product @node @vector(indexes: [{ indexName: "productDescriptionIndex", embeddingProperty: "descriptionVector", queryName: "searchByDescription" @@ -399,7 +399,7 @@ See link:https://neo4j.com/docs/cypher-manual/current/genai-integrations/#ai-pro .Type definition [source, graphql] ---- -type Product @vector(indexes: [{ +type Product @node @vector(indexes: [{ indexName: "productDescriptionIndex", embeddingProperty: "descriptionVector", provider: OPEN_AI, # Assuming this is configured in the server diff --git a/modules/ROOT/pages/directives/schema-configuration/field-configuration.adoc b/modules/ROOT/pages/directives/schema-configuration/field-configuration.adoc index 1296b13f..76fa252b 100644 --- a/modules/ROOT/pages/directives/schema-configuration/field-configuration.adoc +++ b/modules/ROOT/pages/directives/schema-configuration/field-configuration.adoc @@ -8,12 +8,12 @@ In case you need to remove fields from a GraphQL Object Type or a GraphQL Input [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String! description: String } -type Actor { +type Actor @node { name: String! age: Int actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) @@ -38,12 +38,12 @@ For instance, to hide the field `age` in the Selection Set, you can use the `@se [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String! description: String } -type Actor { +type Actor @node { name: String! age: Int @selectable(onRead: false, onAggregate: false) actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) @@ -116,12 +116,12 @@ It is possible to configure this behavior by passing the argument aggregate on t [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String! description: String } -type Actor { +type Actor @node { name: String! age: Int actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, aggregate: false) @@ -168,12 +168,12 @@ To disable the nested `CREATE` operation, change the initial type definitions to [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String! description: String } -type Actor { +type Actor @node { name: String! age: Int actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, nestedOperations: [UPDATE, DELETE, CONNECT, DISCONNECT, CONNECT_OR_CREATE]) @@ -188,12 +188,12 @@ If instead, no nested operations are required, it is possible to disable all the [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String! description: String } -type Actor { +type Actor @node { name: String! age: Int actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, nestedOperations: []) @@ -222,7 +222,7 @@ With the following definition: [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String! description: String @selectable(onRead: false, onAggregate: true) } @@ -253,7 +253,7 @@ In case you want to remove the `description` field from `MovieAggregateSelection [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String! description: String @selectable(onRead: false, onAggregate: false) } @@ -281,12 +281,12 @@ For instance, these type definitions: [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String! description: String } -type Actor { +type Actor @node { name: String! actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) @@ -329,12 +329,12 @@ With this definition: [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String! description: String @settable(onCreate: true, onUpdate: false) } -type Actor { +type Actor @node { name: String! actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) @@ -365,12 +365,12 @@ For example: [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String! description: String } -type Actor { +type Actor @node { name: String! actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) @@ -416,13 +416,13 @@ With this definition: [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String! description: String @filterable(byValue: false, byAggregate: false) actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) } -type Actor { +type Actor @node { name: String! actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) @@ -485,13 +485,13 @@ For example: [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String! description: String @filterable(byValue: false, byAggregate: false) actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) @filterable(byValue: false, byAggregate: false) } -type Actor { +type Actor @node { name: String! actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) diff --git a/modules/ROOT/pages/directives/schema-configuration/global-configuration.adoc b/modules/ROOT/pages/directives/schema-configuration/global-configuration.adoc index 7df40eca..4fbcd1f5 100644 --- a/modules/ROOT/pages/directives/schema-configuration/global-configuration.adoc +++ b/modules/ROOT/pages/directives/schema-configuration/global-configuration.adoc @@ -11,12 +11,12 @@ For instance, if you want *to disable all top-level aggregation operations at on [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) } -type Actor { +type Actor @node { name: String movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) } @@ -40,12 +40,12 @@ Take the following type definitions as example: [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) } -type Actor @query(read: false, aggregate: true) { +type Actor @node @query(read: false, aggregate: true) { name: String movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) } diff --git a/modules/ROOT/pages/directives/schema-configuration/type-configuration.adoc b/modules/ROOT/pages/directives/schema-configuration/type-configuration.adoc index 674b3f86..28227525 100644 --- a/modules/ROOT/pages/directives/schema-configuration/type-configuration.adoc +++ b/modules/ROOT/pages/directives/schema-configuration/type-configuration.adoc @@ -9,7 +9,7 @@ For example: [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String length: Int } @@ -54,7 +54,7 @@ directive @query(read: Boolean! = true, aggregate: Boolean! = false) on OBJECT | .Disable _movies_ and _moviesConnection_ operations [source, graphql, indent=0] ---- -type Movie @query(read: false, aggregate: true) { +type Movie @node @query(read: false, aggregate: true) { title: String length: Int } @@ -63,7 +63,7 @@ type Movie @query(read: false, aggregate: true) { .Disable _moviesAggregate_ operations [source, graphql, indent=0] ---- -type Movie @query(read: true, aggregate: false) { +type Movie @node @query(read: true, aggregate: false) { title: String length: Int } @@ -91,7 +91,7 @@ directive @mutation(operations: [MutationFields!]! = [CREATE, UPDATE, DELETE]) o .Disable Create, Delete, and Update operations for _Movie_ [source, graphql, indent=0] ---- -type Movie @mutation(operations: []) { +type Movie @node @mutation(operations: []) { title: String length: Int } @@ -100,7 +100,7 @@ type Movie @mutation(operations: []) { .Enable only Create operations for _Movie_ [source, graphql, indent=0] ---- -type Movie @mutation(operations: [CREATE]) { +type Movie @node @mutation(operations: [CREATE]) { title: String length: Int } @@ -130,7 +130,7 @@ directive @subscription(events: [SubscriptionFields!]! = [CREATED, UPDATED, DELE .Disable subscriptions for _Movie_ [source, graphql, indent=0] ---- -type Movie @subscription(events: []) { +type Movie @node @subscription(events: []) { title: String length: Int } @@ -139,7 +139,7 @@ type Movie @subscription(events: []) { .Enable only _movieCreated_ subscription for _Movie_ [source, graphql, indent=0] ---- -type Movie @subscription(events: [CREATED]) { +type Movie @node @subscription(events: [CREATED]) { title: String length: Int } @@ -177,7 +177,7 @@ enum Location { EVERYWHERE } -type SomeType { +type SomeType @node { firstLocation: Location! @default(value: HERE) # valid usage secondLocation: Location! @default(value: ELSEWHERE) # invalid usage, will throw an error } @@ -193,7 +193,7 @@ Take this type definition as an example: [source, graphql, indent=0] ---- -type Tech @plural(value: "Techs") { +type Tech @plural(value: "Techs") @node { name: String } ---- diff --git a/modules/ROOT/pages/driver-configuration.adoc b/modules/ROOT/pages/driver-configuration.adoc index 3acba6ee..6b760192 100644 --- a/modules/ROOT/pages/driver-configuration.adoc +++ b/modules/ROOT/pages/driver-configuration.adoc @@ -22,7 +22,7 @@ import { Neo4jGraphQL } from "@neo4j/graphql"; import neo4j from "neo4j-driver"; const typeDefs = `#graphql - type User { + type User @node { name: String } `; @@ -54,7 +54,7 @@ import { Neo4jGraphQL } from "@neo4j/graphql"; import neo4j from "neo4j-driver"; const typeDefs = `#graphql - type User { + type User @node { name: String } `; @@ -86,7 +86,7 @@ import { Neo4jGraphQL } from "@neo4j/graphql"; import neo4j from "neo4j-driver"; const typeDefs = `#graphql - type User { + type User @node { name: String } `; @@ -119,7 +119,7 @@ import { Neo4jGraphQL } from "@neo4j/graphql"; import neo4j from "neo4j-driver"; const typeDefs = `#graphql - type User { + type User @node { name: String } `; @@ -151,7 +151,7 @@ import { OGM } from "@neo4j/graphql-ogm"; import neo4j from "neo4j-driver"; const typeDefs = `#graphql - type User { + type User @node { name: String } `; @@ -178,7 +178,7 @@ import { Neo4jGraphQL } from "@neo4j/graphql"; import neo4j from "neo4j-driver"; const typeDefs = `#graphql - type User { + type User @node { name: String } `; @@ -200,7 +200,7 @@ import { OGM } from "@neo4j/graphql-ogm"; import neo4j from "neo4j-driver"; const typeDefs = `#graphql - type User { + type User @node { name: String } `; @@ -226,7 +226,7 @@ import { Neo4jGraphQL } from "@neo4j/graphql"; import neo4j from "neo4j-driver"; const typeDefs = `#graphql - type User { + type User @node { name: String } `; diff --git a/modules/ROOT/pages/getting-started/index.adoc b/modules/ROOT/pages/getting-started/index.adoc index cc991eb6..2320ba46 100644 --- a/modules/ROOT/pages/getting-started/index.adoc +++ b/modules/ROOT/pages/getting-started/index.adoc @@ -75,12 +75,12 @@ import { Neo4jGraphQL } from "@neo4j/graphql"; import neo4j from "neo4j-driver"; const typeDefs = `#graphql - type Movie { + type Movie @node { title: String actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) } - type Actor { + type Actor @node { name: String movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) } @@ -148,12 +148,12 @@ import { Neo4jGraphQL } from "@neo4j/graphql"; import neo4j from "neo4j-driver"; const typeDefs = `#graphql - type Movie { + type Movie @node { title: String actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) } - type Actor { + type Actor @node { name: String movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) } diff --git a/modules/ROOT/pages/getting-started/toolbox.adoc b/modules/ROOT/pages/getting-started/toolbox.adoc index f12dbf08..6a736819 100644 --- a/modules/ROOT/pages/getting-started/toolbox.adoc +++ b/modules/ROOT/pages/getting-started/toolbox.adoc @@ -25,12 +25,12 @@ If you followed the xref:getting-started/index.adoc[Getting started tutorial], y + [source, graphql, indent=0] ---- -type Actor { +type Actor @node { actedInMovies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) name: String! } -type Movie { +type Movie @node { actorsActedIn: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) title: String! } diff --git a/modules/ROOT/pages/integrations/apollo-federation.adoc b/modules/ROOT/pages/integrations/apollo-federation.adoc index c8da5b98..071d6270 100644 --- a/modules/ROOT/pages/integrations/apollo-federation.adoc +++ b/modules/ROOT/pages/integrations/apollo-federation.adoc @@ -15,7 +15,7 @@ import { Neo4jGraphQL } from "@neo4j/graphql"; import neo4j from "neo4j-driver"; const typeDefs = `#graphql - type User { + type User @node { id: ID! name: String! } @@ -177,7 +177,7 @@ For a set of type definitions to be usable as a subgraph for Federation, they mu const typeDefs = `#graphql extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"]) - type User { + type User @node { id: ID! name: String! } @@ -200,7 +200,7 @@ To achieve that, use the `@key` directive to designate a field (or fields) as a const typeDefs = `#graphql extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"]) - type User @key(fields: "id") { + type User @key(fields: "id") @node { id: ID! name: String! } @@ -231,7 +231,7 @@ import { startStandaloneServer } from "@apollo/server/standalone"; import { Neo4jGraphQL } from "@neo4j/graphql"; const typeDefs = `#graphql - type User @key(fields: "id") { + type User @key(fields: "id") @node { id: ID! name: String! } diff --git a/modules/ROOT/pages/integrations/relay-compatibility.adoc b/modules/ROOT/pages/integrations/relay-compatibility.adoc index 351b5022..045362c2 100644 --- a/modules/ROOT/pages/integrations/relay-compatibility.adoc +++ b/modules/ROOT/pages/integrations/relay-compatibility.adoc @@ -26,7 +26,7 @@ So for example, a type `Book` might have the definition: [source, graphql] ---- -type Book implements Node { +type Book implements Node @node { id: ID! isbn: String! } @@ -51,7 +51,7 @@ You can configure this like so: [source, graphql] ---- -type Book { +type Book @node { isbn: String! @relayId } ---- @@ -60,7 +60,7 @@ When the schema is augmented, this type will be output as: [source, graphql] ---- -type Book implements Node { +type Book implements Node @node { id: ID! isbn: String! } diff --git a/modules/ROOT/pages/mutations/create.adoc b/modules/ROOT/pages/mutations/create.adoc index 4ff25476..66eb7e8e 100644 --- a/modules/ROOT/pages/mutations/create.adoc +++ b/modules/ROOT/pages/mutations/create.adoc @@ -6,13 +6,13 @@ Using the following type definitions: [source, graphql, indent=0] ---- -type Post { +type Post @node { id: ID! @id content: String! creator: User! @relationship(type: "HAS_POST", direction: IN) } -type User { +type User @node { id: ID! @id name: String posts: [Post!]! @relationship(type: "HAS_POST", direction: OUT) @@ -118,12 +118,12 @@ Consider the following type definitions: [source, graphql, indent=0] ---- -type Actor { +type Actor @node { name: String! movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) } -type Movie { +type Movie @node { title: String id: ID! @id @unique actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) diff --git a/modules/ROOT/pages/mutations/delete.adoc b/modules/ROOT/pages/mutations/delete.adoc index 7f318fa0..04d1657a 100644 --- a/modules/ROOT/pages/mutations/delete.adoc +++ b/modules/ROOT/pages/mutations/delete.adoc @@ -8,13 +8,13 @@ Using these type definitions: [source, graphql, indent=0] ---- -type Post { +type Post @node { id: ID! @id content: String! creator: User! @relationship(type: "HAS_POST", direction: IN) } -type User { +type User @node { id: ID! @id name: String posts: [Post!]! @relationship(type: "HAS_POST", direction: OUT) diff --git a/modules/ROOT/pages/mutations/update.adoc b/modules/ROOT/pages/mutations/update.adoc index 569adbb1..c23c2378 100644 --- a/modules/ROOT/pages/mutations/update.adoc +++ b/modules/ROOT/pages/mutations/update.adoc @@ -8,13 +8,13 @@ Using these type definitions: [source, graphql, indent=0] ---- -type Post { +type Post @node { id: ID! @id content: String! creator: User! @relationship(type: "HAS_POST", direction: IN) } -type User { +type User @node { id: ID! @id name: String posts: [Post!]! @relationship(type: "HAS_POST", direction: OUT) @@ -167,7 +167,7 @@ Consider the following type definitions, a `Movie` with a property array called [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String tags: [String] } @@ -233,7 +233,7 @@ Similarly, you can have multiple array property fields and update them in the sa [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String tags: [String] moreTags: [String] @@ -322,7 +322,7 @@ Similarly, you can have multiple array property fields and update them in the sa [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String tags: [String] moreTags: [String] @@ -368,7 +368,7 @@ Consider the following type definitions: [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String tags: [String] moreTags: [String] @@ -439,13 +439,13 @@ For example, take the following GraphQL schema for a social video platform: [source, graphql, indent=0] ---- -type Video { +type Video @node { id: ID @id views: Int ownedBy: User @relationship(type: "OWN_VIDEO", properties: "OwnVideo", direction: IN) } -type User { +type User @node { id: ID @id ownVideo: [Video!]! @relationship(type: "OWN_VIDEO", properties: "OwnVideo", direction: OUT) } diff --git a/modules/ROOT/pages/ogm/directives.adoc b/modules/ROOT/pages/ogm/directives.adoc index d303fd06..6144a28f 100644 --- a/modules/ROOT/pages/ogm/directives.adoc +++ b/modules/ROOT/pages/ogm/directives.adoc @@ -24,7 +24,7 @@ Given the following type definition: [source, graphql, indent=0] ---- -type User { +type User @node { username: String! email: String! password: String! @private @@ -49,7 +49,7 @@ const driver = neo4j.driver( ); const typeDefs = ` - type User { + type User @node { username: String! email: String! password: String! @private diff --git a/modules/ROOT/pages/ogm/installation.adoc b/modules/ROOT/pages/ogm/installation.adoc index 8a63d75c..e6c6c513 100644 --- a/modules/ROOT/pages/ogm/installation.adoc +++ b/modules/ROOT/pages/ogm/installation.adoc @@ -62,7 +62,7 @@ const driver = neo4j.driver( ); const typeDefs = `#graphql - type User { + type User @node { id: ID @id username: String! password: String! @private @@ -257,7 +257,7 @@ const driver = neo4j.driver( ); const typeDefs = ` - type User { + type User @node { id: ID name: String } diff --git a/modules/ROOT/pages/ogm/reference.adoc b/modules/ROOT/pages/ogm/reference.adoc index 4a2bd2e2..0ded4793 100644 --- a/modules/ROOT/pages/ogm/reference.adoc +++ b/modules/ROOT/pages/ogm/reference.adoc @@ -42,7 +42,7 @@ a| .Type definition [source, graphql, indent=0] ---- -type User { +type User @node { username: String! } ---- @@ -68,7 +68,7 @@ a| import { OGM, generate } from "@neo4j/graphql-ogm"; const typeDefs = ` - type Movie { + type Movie @node { id: ID name: String } @@ -95,7 +95,7 @@ console.log("Types Generated"); import { OGM, generate } from "@neo4j/graphql-ogm"; const typeDefs = ` - type Movie { + type Movie @node { id: ID name: String } @@ -127,7 +127,7 @@ Given the type definitions saved to the variable `typeDefs` and a valid driver i [source, graphql, indent=0] ---- -type Book { +type Book @node { isbn: String! @unique } ---- diff --git a/modules/ROOT/pages/ogm/selection-set.adoc b/modules/ROOT/pages/ogm/selection-set.adoc index 7efaae5c..cade6ed2 100644 --- a/modules/ROOT/pages/ogm/selection-set.adoc +++ b/modules/ROOT/pages/ogm/selection-set.adoc @@ -42,14 +42,14 @@ Given the following type definition: [source, graphql, indent=0] ---- -type Movie { +type Movie @node { id: ID name: String genres: [Genre!]! @relationship(type: "IN_GENRE", direction: OUT) customCypher: String! @cypher(statement: "RETURN someCustomData") } -type Genre { +type Genre @node { name: String } ---- @@ -86,14 +86,14 @@ const driver = neo4j.driver( ); const typeDefs = ` - type Movie { + type Movie @node { id: ID name: String genres: [Genre!]! @relationship(type: "IN_GENRE", direction: OUT) customCypher: String! @cypher(statement: "RETURN someCustomData") } - type Genre { + type Genre @node { name: String } `; @@ -139,14 +139,14 @@ const driver = neo4j.driver( ); const typeDefs = ` - type Movie { + type Movie @node { id: ID name: String genres: [Genre!]! @relationship(type: "IN_GENRE", direction: OUT) customCypher: String! @cypher(statement: "RETURN someCustomData") } - type Genre { + type Genre @node { name: String } `; diff --git a/modules/ROOT/pages/ogm/subscriptions.adoc b/modules/ROOT/pages/ogm/subscriptions.adoc index 12125c63..7c3120d0 100644 --- a/modules/ROOT/pages/ogm/subscriptions.adoc +++ b/modules/ROOT/pages/ogm/subscriptions.adoc @@ -14,7 +14,7 @@ Use the following type definitions: [source, javascript, indent=0] ---- const typeDefs = `#graphql - type User { + type User @node { email: String! password: String! @private } @@ -103,7 +103,7 @@ const driver = neo4j.driver( neo4j.auth.basic("neo4j", "password") ); const typeDefs = ` - type User { + type User @node { email: String! password: String! @private } diff --git a/modules/ROOT/pages/ogm/type-generation.adoc b/modules/ROOT/pages/ogm/type-generation.adoc index 66fdb8a3..b02d745f 100644 --- a/modules/ROOT/pages/ogm/type-generation.adoc +++ b/modules/ROOT/pages/ogm/type-generation.adoc @@ -24,7 +24,7 @@ import * as neo4j from "neo4j-driver" import * as path from "path" const typeDefs = ` - type Movie { + type Movie @node { id: ID name: String } diff --git a/modules/ROOT/pages/queries-aggregations/aggregations.adoc b/modules/ROOT/pages/queries-aggregations/aggregations.adoc index 377f0c12..c4ced6e3 100644 --- a/modules/ROOT/pages/queries-aggregations/aggregations.adoc +++ b/modules/ROOT/pages/queries-aggregations/aggregations.adoc @@ -8,14 +8,14 @@ Quries on this page assume the following type definitions: [source, graphql, indent=0] ---- -type Post { +type Post @node { id: ID! @id content: String! creator: User! @relationship(type: "HAS_POST", direction: IN, properties: "PostedAt") createdAt: DateTime! } -type User { +type User @node { id: ID! @id name: String! age: Int! diff --git a/modules/ROOT/pages/queries-aggregations/filtering.adoc b/modules/ROOT/pages/queries-aggregations/filtering.adoc index d1c62515..a8d25c6a 100644 --- a/modules/ROOT/pages/queries-aggregations/filtering.adoc +++ b/modules/ROOT/pages/queries-aggregations/filtering.adoc @@ -103,7 +103,108 @@ query { Spatial types use numerical filtering differently and they also have additional options. See xref:filtering.adoc#_filtering_spatial_types[Filtering spatial types] for more information. -==== Spatial type filtering +[source, javascript, indent=0] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const neo4j = require("neo4j-driver"); + +const typeDefs = ` + type User @node { + name: String + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("username", "password") +); + +const features = { + filters: { + String: { + LT: true, + GT: true, + LTE: true, + GTE: true + } + } +}; + +const neoSchema = new Neo4jGraphQL({ features, typeDefs, driver }); +---- + +== RegEx matching + +The filter `_MATCHES` is also available for comparison of `String` and `ID` types. +It accepts RegEx strings as an argument and returns any matches. + + +Note that RegEx matching filters are **disabled by default**. +This is because, on an unprotected API, they could potentially be used to execute a https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS[ReDoS attack^] against the backing Neo4j database. + +If you want to enable them, set the features configuration object for each: + +[source, javascript, indent=0] +---- +const features = { + filters: { + String: { + MATCHES: true, + } + } +}; + +const neoSchema = new Neo4jGraphQL({ features, typeDefs, driver }); +---- + +For `ID`: + + +[source, javascript, indent=0] +---- +const features = { + filters: { + String: { + ID: true, + } + } +}; + +const neoSchema = new Neo4jGraphQL({ features, typeDefs, driver }); +---- + +For both `String` and `ID`: + + +[source, javascript, indent=0] +---- +const features = { + filters: { + String: { + MATCHES: true, + }, + ID: { + MATCHES: true, + } + } +}; + +const neoSchema = new Neo4jGraphQL({ features, typeDefs, driver }); +---- + +== Array comparison + +The following operator is available on non-array fields, and accepts an array argument: + +* `_IN` + +Conversely, the following operator is available on array fields, and accepts a single argument: + +* `_INCLUDES` + +These operators are available for all types apart from `Boolean`. + +== Filtering spatial types Both the `Point` and the `CartesianPoint` types use xref::queries-aggregations/filtering.adoc#_numerical_operators[numerical operators] and have an additional `_DISTANCE` filter. Here is a list of what each filter does for the two types: @@ -351,13 +452,13 @@ For example, take these type definitions: [source, graphql, indent=0] ---- -type User { +type User @node { id: ID! name: String posts: [Post!]! @relationship(type: "HAS_POST", direction: OUT) } -type Post { +type Post @node { id: ID! content: String author: User! @relationship(type: "HAS_POST", direction: IN) @@ -450,11 +551,11 @@ Here are some examples on how to apply this kind of filtering: .Schema example [source, graphql, indent=0] ---- -type User { +type User @node { name: String } -type Post { +type Post @node { content: String likes: [User!]! @relationship(type: "LIKES", direction: IN) } @@ -475,12 +576,12 @@ query { .Schema example [source, graphql, indent=0] ---- -type Passenger { +type Passenger @node { name: String age: Int } -type Flight { +type Flight @node { code: String passengers: [Passenger!]! @relationship(type: "FLYING_ON", direction: IN) } @@ -501,12 +602,12 @@ query { .Schema example [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") } -type Person { +type Person @node { name: String } @@ -580,7 +681,7 @@ a| .Type definitions [source, graphql, indent=0] ---- -type Event { +type Event @node { title: String! startTime: DateTime! } @@ -603,7 +704,7 @@ a| .Type definitions [source, graphql, indent=0] ---- -type Event { +type Event @node { title: String! duration: Duration! } diff --git a/modules/ROOT/pages/queries-aggregations/pagination/cursor-based.adoc b/modules/ROOT/pages/queries-aggregations/pagination/cursor-based.adoc index db85f7a4..b004beed 100644 --- a/modules/ROOT/pages/queries-aggregations/pagination/cursor-based.adoc +++ b/modules/ROOT/pages/queries-aggregations/pagination/cursor-based.adoc @@ -9,12 +9,12 @@ Using the following type definition: [source, graphql, indent=0] ---- -type User { +type User @node { name: String! posts: [Post!]! @relationship(type: "HAS_POST", direction: OUT) } -type Post { +type Post @node { content: String! } ---- diff --git a/modules/ROOT/pages/queries-aggregations/pagination/offset-based.adoc b/modules/ROOT/pages/queries-aggregations/pagination/offset-based.adoc index f73d5581..a9892873 100644 --- a/modules/ROOT/pages/queries-aggregations/pagination/offset-based.adoc +++ b/modules/ROOT/pages/queries-aggregations/pagination/offset-based.adoc @@ -8,7 +8,7 @@ Using the following type definition: [source, graphql, indent=0] ---- -type User { +type User @node { name: String! } ---- diff --git a/modules/ROOT/pages/queries-aggregations/queries.adoc b/modules/ROOT/pages/queries-aggregations/queries.adoc index 8697f564..01750e47 100644 --- a/modules/ROOT/pages/queries-aggregations/queries.adoc +++ b/modules/ROOT/pages/queries-aggregations/queries.adoc @@ -8,14 +8,14 @@ Quries on this page assume the following type definitions: [source, graphql, indent=0] ---- -type Post { +type Post @node { id: ID! @id content: String! creator: User! @relationship(type: "HAS_POST", direction: IN, properties: "PostedAt") createdAt: DateTime! } -type User { +type User @node { id: ID! @id name: String! age: Int! diff --git a/modules/ROOT/pages/queries-aggregations/sorting.adoc b/modules/ROOT/pages/queries-aggregations/sorting.adoc index 50956ebb..857b77f8 100644 --- a/modules/ROOT/pages/queries-aggregations/sorting.adoc +++ b/modules/ROOT/pages/queries-aggregations/sorting.adoc @@ -10,7 +10,7 @@ Using this example type definition: [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String! runtime: Int! } @@ -20,7 +20,7 @@ The following sorting input type and query are generated: [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String! runtime: Int! } diff --git a/modules/ROOT/pages/security/authentication.adoc b/modules/ROOT/pages/security/authentication.adoc index b8a089a8..26552c94 100644 --- a/modules/ROOT/pages/security/authentication.adoc +++ b/modules/ROOT/pages/security/authentication.adoc @@ -9,7 +9,44 @@ Explicit authentication, configured with the `@authentication` directive, is onl Unauthenticated requests with queries requiring authentication never reach the database. ==== -== Operations +Authentication can be configured for an entire type, for example, the type `User`: + +[source, graphql, indent=0] +---- +type User @authentication @node { + id: ID! + name: String! + password: String! +} +---- + +Authentication will thus be validated when any of the following operations are _attempted_: + +* *Create*: `createUsers` mutation, `create`, or `connectOrCreate` nested operation via a related type. +* *Read*: `users`, `usersConnection`, `usersAggregate` query, or access via related type. +* *Update*: `updateUsers` mutation or `update` nested operation via a related type. +* *Delete*: `deleteUsers` mutation or `delete` nested operation via a related type. +* *Create relationship*: `connect` or `connectOrCreate` nested operation via a related type. +* *Delete relationship*: `disconnect` nested operation via a related type. +* *Subscribe*: all subscription operations related to type `User`. + +Additionally, the directive can be configured on a per-field basis, for example: + +[source, graphql, indent=0] +---- +type User @node { + id: ID! + name: String! + password: String! @authentication +} +---- + +This will only be evaluated in the following circumstances: + +* The `password` field is set on either `create` or `update`. +* The `password` field is present in a selection set. + +=== Operations Authentication can be configured to only be validated on certain operations: @@ -26,7 +63,7 @@ For instance, to only require authentication for the update or deletion of a use [source, graphql, indent=0] ---- -type User @authentication(operations: [UPDATE, DELETE]) { +type User @authentication(operations: [UPDATE, DELETE]) @node { id: ID! name: String! password: String! @@ -109,7 +146,7 @@ For instance, if it was a requirement that only users with the `admin` role can [source, graphql, indent=0] ---- -type User @authentication(operations: [DELETE], jwt: { roles_INCLUDES: "admin" }) { +type User @authentication(operations: [DELETE], jwt: { roles_INCLUDES: "admin" }) @node { id: ID! name: String! password: String! diff --git a/modules/ROOT/pages/security/authorization.adoc b/modules/ROOT/pages/security/authorization.adoc index 39a3f5f0..db0abf8b 100644 --- a/modules/ROOT/pages/security/authorization.adoc +++ b/modules/ROOT/pages/security/authorization.adoc @@ -29,11 +29,11 @@ For instance, here is how to filter out `Post` nodes which don't belong to the c [source, graphql, indent=0] ---- -type User { +type User @node { id: ID! } -type Post @authorization(filter: [ +type Post @node @authorization(filter: [ { where: { node: { author: { id: "$jwt.sub" } } } } ]) { title: String! @@ -57,7 +57,7 @@ For instance, to only require filtering for the reading and aggregating posts: [source, graphql, indent=0] ---- -type Post @authorization(filter: [ +type Post @node @authorization(filter: [ { operations: [READ, AGGREGATE] where: { node: { author: { id: "$jwt.sub" } } } } ]) { title: String! @@ -86,7 +86,7 @@ type JWT @jwt { roles: [String!]! } -type User @authorization(validate: [ +type User @node @authorization(validate: [ { where: { node: { id: "$jwt.sub" } } } { where: { jwt: { roles_INCLUDES: "admin" } } } ]) { @@ -111,7 +111,7 @@ For instance, to only require validation for the update or deletion of a post: [source, graphql, indent=0] ---- -type Post @authorization(validate: [ +type Post @node @authorization(validate: [ { operations: [UPDATE, DELETE] where: { node: { author: { id: "$jwt.sub" } } } } ]) { title: String! @@ -135,11 +135,11 @@ For instance, in the case where some `Post` nodes are private and belong to a pa [source, graphql, indent=0] ---- -type User { +type User @node { id: ID! } -type Post @authorization(filter: [ +type Post @node @authorization(filter: [ { where: { node: { author: { id: "$jwt.sub" } } } } { requireAuthentication: false, operations: [READ], where: { node: { public: true } } } ]) { diff --git a/modules/ROOT/pages/security/impersonation-and-user-switching.adoc b/modules/ROOT/pages/security/impersonation-and-user-switching.adoc index 7d0585e5..76ddb3f4 100644 --- a/modules/ROOT/pages/security/impersonation-and-user-switching.adoc +++ b/modules/ROOT/pages/security/impersonation-and-user-switching.adoc @@ -25,7 +25,7 @@ import { Neo4jGraphQL } from "@neo4j/graphql"; import neo4j from "neo4j-driver"; const typeDefs = `#graphql - type Movie { + type Movie @node { title: String! } `; @@ -69,7 +69,7 @@ import { Neo4jGraphQL, Neo4jGraphQLContext } from "@neo4j/graphql"; import neo4j from "neo4j-driver"; const typeDefs = `#graphql - type Movie { + type Movie @node { title: String! } `; @@ -123,7 +123,7 @@ import { Neo4jGraphQL } from "@neo4j/graphql"; import neo4j from "neo4j-driver"; const typeDefs = `#graphql - type Movie { + type Movie @node { title: String! } `; @@ -167,7 +167,7 @@ import { Neo4jGraphQL, Neo4jGraphQLContext } from "@neo4j/graphql"; import neo4j from "neo4j-driver"; const typeDefs = `#graphql - type Movie { + type Movie @node { title: String! } `; diff --git a/modules/ROOT/pages/security/operations.adoc b/modules/ROOT/pages/security/operations.adoc index 5c40095b..0d258160 100644 --- a/modules/ROOT/pages/security/operations.adoc +++ b/modules/ROOT/pages/security/operations.adoc @@ -8,7 +8,7 @@ Each relevant line has a comment such as `CREATE ON OBJECT Movie`, which means a [source, graphql, indent=0] ---- -type Movie @authentication(operations: [CREATE]) { +type Movie @authentication(operations: [CREATE]) @node { title: String! actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) } diff --git a/modules/ROOT/pages/security/subscriptions-authorization.adoc b/modules/ROOT/pages/security/subscriptions-authorization.adoc index cb0490f2..da69784f 100644 --- a/modules/ROOT/pages/security/subscriptions-authorization.adoc +++ b/modules/ROOT/pages/security/subscriptions-authorization.adoc @@ -18,7 +18,7 @@ For instance, here is how to filter out `User` events which don't match the JWT [source, graphql, indent=0] ---- -type User @subscriptionsAuthorization(filter: [ +type User @node @subscriptionsAuthorization(filter: [ { where: { node: { id: "$jwt.sub" } } } ]) { id: ID! @@ -39,7 +39,7 @@ For instance, to only require filtering for mutations to a type itself and not i [source, graphql, indent=0] ---- -type User @subscriptionsAuthorization(filter: [ +type User @node @subscriptionsAuthorization(filter: [ { events: [CREATED, UPDATED, DELETED], where: { node: { id: "$jwt.sub" } } } ]) { id: ID! @@ -55,7 +55,7 @@ For instance, in the case where some `Post` nodes are private whilst other `Post [source, graphql, indent=0] ---- -type Post @subscriptionsAuthorization(filter: [ +type Post @node @subscriptionsAuthorization(filter: [ { requireAuthentication: false, where: { node: { public: true } } } ]) { title: String! diff --git a/modules/ROOT/pages/subscriptions/events.adoc b/modules/ROOT/pages/subscriptions/events.adoc index 5f98bf47..2e38083a 100644 --- a/modules/ROOT/pages/subscriptions/events.adoc +++ b/modules/ROOT/pages/subscriptions/events.adoc @@ -29,7 +29,7 @@ As an example, consider the following type definitions: [source,graphql,indent=0] ---- -type Movie { +type Movie @node { title: String genre: String } @@ -68,7 +68,7 @@ As an example, consider the following type definitions: [source,graphql,indent=0] ---- -type Movie { +type Movie @node { title: String genre: String } @@ -107,7 +107,7 @@ As an example, consider the following type definitions: [source,graphql,indent=0] ---- -type Movie { +type Movie @node { title: String genre: String } @@ -167,14 +167,14 @@ As an example, consider the following type definitions: [source,graphql,indent=0] ---- -type Movie { +type Movie @node { title: String genre: String actors: [Actor] @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") reviewers: [Reviewer] @relationship(type: "REVIEWED", direction: IN, properties: "Reviewed") } -type Actor { +type Actor @node { name: String } @@ -182,7 +182,7 @@ type ActedIn @relationshipProperties { screenTime: Int! } -type Reviewer { +type Reviewer @node { name: String reputation: Int } @@ -244,13 +244,13 @@ For another example, this time creating a relationship with standard types, cons [source,graphql,indent=0] ---- -type Movie { +type Movie @node { title: String genre: String actors: [Actor] @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") } -type Actor { +type Actor @node { name: String } @@ -294,7 +294,7 @@ As an example, consider the following type definitions: [source,graphql,indent=0] ---- -type Movie { +type Movie @node { title: String genre: String directors: [Director!]! @relationship(type: "DIRECTED", properties: "Directed", direction: IN) @@ -302,11 +302,11 @@ type Movie { union Director = Person | Actor -type Actor { +type Actor @node { name: String } -type Person { +type Person @node { name: String reputation: Int } @@ -353,7 +353,7 @@ For an example in which a relationship is created with an interface, consider th [source,graphql,indent=0] ---- -type Movie { +type Movie @node { title: String genre: String reviewers: [Reviewer!]! @relationship(type: "REVIEWED", properties: "Review", direction: IN) @@ -363,12 +363,12 @@ interface Reviewer { reputation: Int! } -type Magazine implements Reviewer { +type Magazine implements Reviewer @node { title: String reputation: Int! } -type Influencer implements Reviewer { +type Influencer implements Reviewer @node { name: String reputation: Int! } @@ -419,19 +419,19 @@ To illustrate that, consider the following type definitions: [source,graphql,indent=0] ---- -type Movie { +type Movie @node { title: String genre: String actors: [Actor] @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") directors: [Director!]! @relationship(type: "DIRECTED", properties: "Directed", direction: IN) } -type Actor { +type Actor @node { name: String movies: [Movie!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: OUT) } -type Person { +type Person @node { name: String reputation: Int } @@ -703,12 +703,12 @@ type Actor @node(label: "Person") { movies: [Movie!]! @relationship(type: "PART_OF", direction: OUT) } -typePerson { +type Person @node { name: String movies: [Movie!]! @relationship(type: "PART_OF", direction: OUT) } -type Movie { +type Movie @node { title: String genre: String people: [Person!]! @relationship(type: "PART_OF", direction: IN) @@ -746,7 +746,7 @@ subscription { } } } - } + }As an example, consider these type definitions: } subscription { @@ -998,14 +998,14 @@ As an example, consider these type definitions: [source,graphql,indent=0] ---- -type Movie { +type Movie @node { title: String genre: String actors: [Actor] @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") reviewers: [Reviewer] @relationship(type: "REVIEWED", direction: IN, properties: "Reviewed") } -type Actor {s +type Actor @node { name: String } @@ -1013,7 +1013,7 @@ type ActedIn @relationshipProperties { screenTime: Int! } -type Reviewer { +type Reviewer @node { name: String reputation: Int } @@ -1075,13 +1075,13 @@ As an example, consider these type definitions: [source,graphql,indent=0] ---- -type Movie { +type Movie @node { title: String genre: String actors: [Actor] @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") } -type Actor { +type Actor @node { name: String } @@ -1126,7 +1126,7 @@ Considering the following type definitions: [source, graphql] ---- -type Movie { +type Movie @node { title: String genre: String directors: [Director!]! @relationship(type: "DIRECTED", properties: "Directed", direction: IN) @@ -1134,11 +1134,11 @@ type Movie { union Director = Person | Actor -type Actor { +type Actor @node { name: String } -type Person { +type Person @node { name: String reputation: Int } @@ -1184,7 +1184,7 @@ Considering the following type definitions: [source, graphql] ---- -type Movie { +type Movie @node { title: String genre: String reviewers: [Reviewer!]! @relationship(type: "REVIEWED", properties: "Review", direction: IN) @@ -1194,12 +1194,12 @@ interface Reviewer { reputation: Int! } -type Magazine implements Reviewer { +type Magazine implements Reviewer @node { title: String reputation: Int! } -type Influencer implements Reviewer { +type Influencer implements Reviewer @node { name: String reputation: Int! } @@ -1249,19 +1249,19 @@ Considering the following type definitions: [source, graphql] ---- -type Movie { +type Movie @node { title: String genre: String actors: [Actor] @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") directors: [Director!]! @relationship(type: "DIRECTED", properties: "Directed", direction: IN) } -type Actor { +type Actor @node { name: String movies: [Movie!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: OUT) } -type Person { +type Person @node { name: String reputation: Int } @@ -1561,12 +1561,12 @@ type Actor @node(label: "Person") { movies: [Movie!]! @relationship(type: "PART_OF", direction: OUT) } -typePerson { +typePerson @node { name: String movies: [Movie!]! @relationship(type: "PART_OF", direction: OUT) } -type Movie { +type Movie @node { title: String genre: String people: [Person!]! @relationship(type: "PART_OF", direction: IN) diff --git a/modules/ROOT/pages/subscriptions/filtering.adoc b/modules/ROOT/pages/subscriptions/filtering.adoc index 48321f82..168ead0d 100644 --- a/modules/ROOT/pages/subscriptions/filtering.adoc +++ b/modules/ROOT/pages/subscriptions/filtering.adoc @@ -73,7 +73,7 @@ As an example, consider the following type definitions: [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String genre: String averageRating: Float @@ -232,7 +232,7 @@ As an example, in the following type definitions: [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String genre: String actors: [Actor!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN) @@ -242,7 +242,7 @@ type ActedIn @relationshipProperties { screenTime: Int! } -type Actor { +type Actor @node { name: String } ---- @@ -341,7 +341,7 @@ Considering the following type definitions: [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String genre: String actors: [Actor!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN) @@ -353,11 +353,11 @@ type ActedIn @relationshipProperties { screenTime: Int! } -type Actor { +type Actor @node { name: String } -type Person implements Reviewer { +type Person implements Reviewer @node { name: String reputation: Int } @@ -372,12 +372,12 @@ interface Reviewer { reputation: Int! } -type Magazine implements Reviewer { +type Magazine implements Reviewer @node { title: String reputation: Int! } -type Review @relationshipProperties { +type Review @relationshipProperties @node { score: Int! } ---- diff --git a/modules/ROOT/pages/subscriptions/getting-started.adoc b/modules/ROOT/pages/subscriptions/getting-started.adoc index e1c24294..83e45544 100644 --- a/modules/ROOT/pages/subscriptions/getting-started.adoc +++ b/modules/ROOT/pages/subscriptions/getting-started.adoc @@ -55,11 +55,11 @@ import { useServer } from "graphql-ws/lib/use/ws"; import express from 'express'; const typeDefs = ` - type Movie { + type Movie @node { title: String } - type Actor { + type Actor @node { name: String } `; diff --git a/modules/ROOT/pages/troubleshooting.adoc b/modules/ROOT/pages/troubleshooting.adoc index 54b5b9d4..6554b80f 100644 --- a/modules/ROOT/pages/troubleshooting.adoc +++ b/modules/ROOT/pages/troubleshooting.adoc @@ -38,7 +38,7 @@ const neo4j = require("neo4j-driver"); const { ApolloServer } = require("apollo-server"); const typeDefs = ` - type Movie { + type Movie @node { title: String! } `; @@ -84,7 +84,7 @@ const neo4j = require("neo4j-driver"); const { ApolloServer } = require("apollo-server"); const typeDefs = ` - type Movie { + type Movie @node { title: String! } `; @@ -145,7 +145,7 @@ The following example will create inputs with `_emptyInput`: [source, graphql] ---- -type Cookie { +type Cookie @node { id: ID! @id owner: Owner! @relationship(type: "HAS_OWNER", direction: OUT) # f: String # If you don't want _emptyInput, uncomment this line. @@ -158,13 +158,13 @@ Currently, and given the typeDefs below, Neo4j GraphQL will enforce cardinality [source, graphql, indent=0] ---- -type Movie { +type Movie @node { title: String! director: Person! @relationship(type: "DIRECTED", direction: IN) actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN) } -type Person { +type Person @node { name: String! } ---- diff --git a/modules/ROOT/pages/types/interfaces.adoc b/modules/ROOT/pages/types/interfaces.adoc index d691f23b..d3307278 100644 --- a/modules/ROOT/pages/types/interfaces.adoc +++ b/modules/ROOT/pages/types/interfaces.adoc @@ -18,13 +18,13 @@ interface Production { actors: [Actor!]! @declareRelationship } -type Movie implements Production { +type Movie implements Production @node { title: String! actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") runtime: Int! } -type Series implements Production { +type Series implements Production @node { title: String! actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") episodes: Int! @@ -34,7 +34,7 @@ type ActedIn @relationshipProperties { role: String! } -type Actor { +type Actor @node { name: String! actedIn: [Production!]! @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedIn") } diff --git a/modules/ROOT/pages/types/relationships.adoc b/modules/ROOT/pages/types/relationships.adoc index 1a9096ab..99267948 100644 --- a/modules/ROOT/pages/types/relationships.adoc +++ b/modules/ROOT/pages/types/relationships.adoc @@ -19,12 +19,12 @@ To create that graph using the Neo4j GraphQL Library, first you need to define t [source, graphql, indent=0] ---- -type Person { +type Person @node { name: String! born: Int! } -type Movie { +type Movie @node { title: String! released: Int! } @@ -34,14 +34,14 @@ You can then connect these two types together using `@relationship` directives: [source, graphql, indent=0] ---- -type Person { +type Person @node { name: String! born: Int! actedInMovies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) directedMovies: [Movie!]! @relationship(type: "DIRECTED", direction: OUT) } -type Movie { +type Movie @node { title: String! released: Int! actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN) @@ -69,14 +69,14 @@ For example, suppose you want to distinguish which roles an actor played in a mo [source, graphql, indent=0] ---- -type Person { +type Person @node { name: String! born: Int! actedInMovies: [Movie!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: OUT) directedMovies: [Movie!]! @relationship(type: "DIRECTED", direction: OUT) } -type Movie { +type Movie @node { title: String! released: Int! actors: [Person!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN) @@ -99,19 +99,19 @@ interface Production { actors: [Actor!]! @declareRelationship } -type Actor { +type Actor @node { name: String! born: Int! actedInMovies: [Movie!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: OUT) } -type Movie implements Production { +type Movie implements Production @node { title: String! released: Int! actors: [Actor!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN) } -type Series implements Production { +type Series implements Production @node { title: String! released: Int! episodes: Int! @@ -131,7 +131,7 @@ To set the default behavior of a relationship when it is queried, you can use th [source, graphql, indent=0] ---- -type Person { +type Person @node { name: String! born: Int! actedInMovies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, queryDirection: DEFAULT_DIRECTED) @@ -315,12 +315,12 @@ For example: [source, graphql, indent=0] ---- -type User { +type User @node { name: String! posts: [Post!]! @relationship(type: "HAS_POST", direction: OUT) } -type Post { +type Post @node { name: String! } ---- diff --git a/modules/ROOT/pages/types/scalar.adoc b/modules/ROOT/pages/types/scalar.adoc index 438edb11..10e18ee0 100644 --- a/modules/ROOT/pages/types/scalar.adoc +++ b/modules/ROOT/pages/types/scalar.adoc @@ -17,7 +17,7 @@ The `BigInt` scalar type is an addition specific to the Neo4j database. a| [source, graphql, indent=0] ---- -type Person { +type Person @node { age: Int! } ---- @@ -28,7 +28,7 @@ Shares the same xref::queries-aggregations/filtering.adoc#_numerical_operators[N a| [source, graphql, indent=0] ---- -type File { +type File @node { size: BigInt } ---- @@ -49,7 +49,7 @@ query { a| [source, graphql, indent=0] ---- -type Product { +type Product @node { price: Float! } ---- @@ -59,7 +59,7 @@ type Product { a| [source, graphql, indent=0] ---- -type Product { +type Product @node { name: String! } ---- @@ -69,7 +69,7 @@ type Product { a| [source, graphql, indent=0] ---- -type Product { +type Product @node { inStock: Boolean! } ---- @@ -79,7 +79,7 @@ type Product { a| [source, graphql, indent=0] ---- -type Product { +type Product @node { id: ID! } ---- diff --git a/modules/ROOT/pages/types/spatial.adoc b/modules/ROOT/pages/types/spatial.adoc index e9df8501..c611e116 100644 --- a/modules/ROOT/pages/types/spatial.adoc +++ b/modules/ROOT/pages/types/spatial.adoc @@ -18,7 +18,7 @@ In order to use it in your schema, add a field with a type `Point` to any other [source, graphql, indent=0] ---- -type TypeWithPoint { +type TypeWithPoint @node { location: Point! } ---- @@ -31,7 +31,7 @@ See xref::queries-aggregations/filtering.adoc#_filtering_spatial_types[Filtering [source, graphql, indent=0] ---- -type Point { +type Point @node { latitude: Float! longitude: Float! height: Float @@ -93,7 +93,7 @@ To use it in your schema, add a field with a type `CartesianPoint` to any type(s [source, graphql, indent=0] ---- -type TypeWithCartesianPoint { +type TypeWithCartesianPoint @node { location: CartesianPoint! } ---- @@ -106,7 +106,7 @@ See xref::queries-aggregations/filtering.adoc#_filtering_spatial_types[Filtering [source, graphql, indent=0] ---- -type CartesianPoint { +type CartesianPoint @node { x: Float! y: Float! z: Float diff --git a/modules/ROOT/pages/types/temporal.adoc b/modules/ROOT/pages/types/temporal.adoc index 2f884fdf..d0be0571 100644 --- a/modules/ROOT/pages/types/temporal.adoc +++ b/modules/ROOT/pages/types/temporal.adoc @@ -12,7 +12,7 @@ a| [source, graphql, indent=0] ---- -type User { +type User @node { createdAt: DateTime } ---- @@ -22,7 +22,7 @@ type User { a| [source, graphql, indent=0] ---- -type Movie { +type Movie @node { releaseDate: Date } ---- @@ -37,7 +37,7 @@ Comparisons are made according to the https://neo4j.com/developer/cypher/dates-d a| [source, graphql, indent=0] ---- -type Movie { +type Movie @node { runningTime: Duration! } ---- @@ -47,7 +47,7 @@ type Movie { a| [source, graphql, indent=0] ---- -type Movie { +type Movie @node { nextShowing: LocalDateTime } ---- @@ -57,7 +57,7 @@ type Movie { a| [source, graphql, indent=0] ---- -type Movie { +type Movie @node { nextShowing: Time } ---- @@ -67,7 +67,7 @@ type Movie { a| [source, graphql, indent=0] ---- -type Movie { +type Movie @node { nextShowing: LocalTime } ---- diff --git a/modules/ROOT/pages/types/unions.adoc b/modules/ROOT/pages/types/unions.adoc index 4c173d3c..e99bafee 100644 --- a/modules/ROOT/pages/types/unions.adoc +++ b/modules/ROOT/pages/types/unions.adoc @@ -14,16 +14,16 @@ It defines a `User` type that has a relationship `HAS_CONTENT`, of type `[Conten ---- union Content = Blog | Post -type Blog { +type Blog @node { title: String posts: [Post!]! @relationship(type: "HAS_POST", direction: OUT) } -type Post { +type Post @node { content: String } -type User { +type User @node { name: String content: [Content!]! @relationship(type: "HAS_CONTENT", direction: OUT) } From 277f924694db284fe778353943fc33edeef7bfd0 Mon Sep 17 00:00:00 2001 From: MacondoExpress Date: Mon, 9 Sep 2024 09:35:45 +0100 Subject: [PATCH 2/9] fix typo --- modules/ROOT/pages/subscriptions/events.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/pages/subscriptions/events.adoc b/modules/ROOT/pages/subscriptions/events.adoc index 2e38083a..a8f8b3fe 100644 --- a/modules/ROOT/pages/subscriptions/events.adoc +++ b/modules/ROOT/pages/subscriptions/events.adoc @@ -746,7 +746,7 @@ subscription { } } } - }As an example, consider these type definitions: + } } subscription { From cb1d254cb7b800cb24b1136c9f7a8f0751469921 Mon Sep 17 00:00:00 2001 From: MacondoExpress Date: Wed, 11 Sep 2024 10:47:13 +0100 Subject: [PATCH 3/9] Explain importance of the @node directive --- .../pages/directives/database-mapping.adoc | 137 +++++++++--------- 1 file changed, 70 insertions(+), 67 deletions(-) diff --git a/modules/ROOT/pages/directives/database-mapping.adoc b/modules/ROOT/pages/directives/database-mapping.adoc index df75f546..ad882406 100644 --- a/modules/ROOT/pages/directives/database-mapping.adoc +++ b/modules/ROOT/pages/directives/database-mapping.adoc @@ -8,73 +8,11 @@ This page describes how to use directives for database mapping. Each type in your GraphQL type definitions can be mapped to an entity in your Neo4j database, such as nodes, relationships, and relationship properties. -== `@relationship` - -Relationships are represented by marking particular fields with a directive -- in this case, `@relationship`. -It defines the relationship type in the database, as well as which direction that relationship goes in. - -To add a second node type, "Actor", and connect the two together, you should do the following: - -[source, graphql, indent=0] ----- -type Movie @node { - title: String - actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) -} - -type Actor @node { - name: String - movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) -} ----- - -Note that, in this case, there is a directive on each "end" of the relationship, but it is not essential. - - -== `@relationshipProperties` - -=== Definition - -[source, graphql, indent=0] ----- -"""Required to differentiate between interfaces for relationship properties, and otherwise.""" -directive @relationshipProperties on OBJECT ----- - -`@relationshipProperties` can only be used on interfaces. - -=== Usage - -In order to add properties to a relationship, add a new type to your type definitions decorated with the `@relationshipProperties` directive. - -For example, for the "ACTED_IN" relationship, add a property "roles": - -[source, graphql, indent=0] ----- -type Movie @node { - title: String - actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") -} - -type Actor @node { - name: String - movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedIn") -} - -type ActedIn @relationshipProperties { - roles: [String] -} ----- - -Note that in addition to this type, there is an added key `properties` in the existing `@relationship` directives. -For more information, see xref::/types/relationships.adoc[Type definitions -> Relationships]. - - [[type-definitions-node]] == `@node` -The most basic mapping uses GraphQL type names to map to a Neo4j node label. -For example, to represent a node with the label "Movie" and a single property "title" of type string: +Adding the `@node` directive to your GraphQL type specifies that it represents a Neo4j node. +For example, to represent a Neo4j node with the label "Movie" and a single property "title" of type string: [source, graphql, indent=0] ---- @@ -83,9 +21,13 @@ type Movie @node { } ---- -With the `@node` directive, you have more control over this mapping, and you can specify the configuration of a GraphQL object type which represents a Neo4j node. -It can be appended with the following optional parameters: +[NOTE] +==== +In the current version, it's not required to specify every GraphQL type representing a Neo4j node with the `@node` directive. +In the future, types without the `@node` directive will no longer treated as Neo4j nodes. +==== +When not differently specified, the GraphQL type name is used as a label for the represented Neo4j node. It's possible to explicitly define the Neo4j node labels by using the argument `labels`: [discrete] === `labels` @@ -216,6 +158,67 @@ await startStandaloneServer(server, { }); ---- +== `@relationship` + +Relationships are represented by marking particular fields with a directive -- in this case, `@relationship`. +It defines the relationship type in the database, as well as which direction that relationship goes in. + +To add a second node type, "Actor", and connect the two together, you should do the following: + +[source, graphql, indent=0] +---- +type Movie @node { + title: String + actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) +} + +type Actor @node { + name: String + movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) +} +---- + +Note that, in this case, there is a directive on each "end" of the relationship, but it is not essential. + + +== `@relationshipProperties` + +=== Definition + +[source, graphql, indent=0] +---- +"""Required to differentiate between interfaces for relationship properties, and otherwise.""" +directive @relationshipProperties on OBJECT +---- + +`@relationshipProperties` can only be used on interfaces. + +=== Usage + +In order to add properties to a relationship, add a new type to your type definitions decorated with the `@relationshipProperties` directive. + +For example, for the "ACTED_IN" relationship, add a property "roles": + +[source, graphql, indent=0] +---- +type Movie @node { + title: String + actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") +} + +type Actor @node { + name: String + movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedIn") +} + +type ActedIn @relationshipProperties { + roles: [String] +} +---- + +Note that in addition to this type, there is an added key `properties` in the existing `@relationship` directives. +For more information, see xref::/types/relationships.adoc[Type definitions -> Relationships]. + [[type-definitions-alias]] == `@alias` @@ -251,4 +254,4 @@ type UserLivesInProperties @relationshipProperties { ---- [NOTE] -The property in aliases are automatically escaped (wrapped with backticks ``), so there is no need to add escape characters around them. \ No newline at end of file +The property in aliases are automatically escaped (wrapped with backticks ``), so there is no need to add escape characters around them. From 8b539952d9faf80e7eab4a46fb981a6ed5ce2af0 Mon Sep 17 00:00:00 2001 From: MacondoExpress Date: Mon, 14 Oct 2024 14:10:43 +0100 Subject: [PATCH 4/9] add missing @node at array filters --- modules/ROOT/pages/queries-aggregations/filtering.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ROOT/pages/queries-aggregations/filtering.adoc b/modules/ROOT/pages/queries-aggregations/filtering.adoc index a8d25c6a..d4f9dd46 100644 --- a/modules/ROOT/pages/queries-aggregations/filtering.adoc +++ b/modules/ROOT/pages/queries-aggregations/filtering.adoc @@ -387,7 +387,7 @@ Consider the following type definitions: [source, graphql, indent=0] ---- -type Movie { +type Movie @node { id: ID! title: String! genres: [String!] @@ -395,7 +395,7 @@ type Movie { actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) } -type Actor { +type Actor @node { id: ID! name: String! movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) From 3d9363155dbb4b25f7fa9c258e35906ea8aeaa29 Mon Sep 17 00:00:00 2001 From: MacondoExpress Date: Mon, 14 Oct 2024 16:17:15 +0100 Subject: [PATCH 5/9] add migration guide describing the new requirement for @node --- modules/ROOT/pages/migration/index.adoc | 192 ++---------------------- 1 file changed, 16 insertions(+), 176 deletions(-) diff --git a/modules/ROOT/pages/migration/index.adoc b/modules/ROOT/pages/migration/index.adoc index c44176ec..cb768928 100644 --- a/modules/ROOT/pages/migration/index.adoc +++ b/modules/ROOT/pages/migration/index.adoc @@ -1,11 +1,11 @@ -[[v5-migration]] -:description: This page lists the breaking changes from version 4.0.0 to 5.0.0 and describes how to update. +[[v6-migration]] +:description: This page lists the breaking changes from version 5.0.0 to 6.0.0 and describes how to update. :page-aliases: guides/index.adoc, guides/migration-guide/index.adoc, guides/migration-guide/server.adoc, \ guides/migration-guide/queries.adoc, guides/migration-guide/type-definitions.adoc, guides/migration-guide/mutations.adoc -= Migration to 5.0.0 += Migration to 6.0.0 -This page lists all breaking changes from the Neo4j GraphQL Library version 4.x to 5.x and how to update it. +This page lists all breaking changes from the Neo4j GraphQL Library version 5.x to 6.x and how to update it. == How to update @@ -18,12 +18,16 @@ npm update @neo4j/graphql == Breaking changes -Here is a list of all the breaking changes from version 4.0.0 to 5.0.0. +Here is a list of all the breaking changes from version 5.0.0 to 6.0.0. -=== `@relationshipProperties` -The directive `@relationshipProperties` should now be used on types rather than interfaces. -For example: +== Deprecations and Warnings + +=== `@node` will be required to be explicitly defined + +In the future, types without the `@node` directive will no longer be treated as Neo4j nodes. +In version 6.0.0, it's not required to specify every GraphQL type representing a Neo4j node with the `@node` directive, however it's recommended to do so and a warning is raised if not found. +There are genuine cases in which a `@node` should not be used e.g., for type returned by @cypher fields which are not Neo4j nodes. [cols="1,1"] |=== @@ -32,13 +36,9 @@ For example: a| [source, graphql, indent=0] ---- -interface ActedIn @relationshipProperties { - screenTime: Int -} - type Movie { title: String - actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") + actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN) } type Person { @@ -48,174 +48,14 @@ type Person { a| [source, graphql, indent=0] ---- -type ActedIn @relationshipProperties { - screenTime: Int -} - -type Movie { +type Movie @node { title: String - actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") + actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN) } -type Person { +type Person @node { name: String } ---- |=== -=== Interface directives - -To better match the default behavior of GraphQL, library directives used in interfaces will not cascade to the implementing types anymore. -This means that most directives are no longer valid in interfaces and have to be defined in the implementing types. -This also applies to custom directives. - - - -=== `@declareRelationship` - -The `@relationship` directive is no longer available in interfaces. -If you need a relationship to be available on interfaces, you need to use the new `@declareRelationship` directive instead, as well as define the relationships in the concrete type. - -This change is due to directives no longer cascading from interfaces to types. -However, now it is possible for a relationship to have different properties and labels in each type. -For example: - -[cols="1,1"] -|=== -|Before | Now -a| -[source, graphql, indent=0] ----- -interface Production { - title: String! - actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) -} - -type Movie implements Production { - title: String! - actors: [Actor!]! -} - -type Series implements Production { - title: String! - episodes: Int! - actors: [Actor!]! -} - -type Actor { - name: String! -} ----- -a| -[source, graphql, indent=0] ----- -interface Production { - title: String! - actors: [Actor!]! @declareRelationship -} - -type Movie implements Production { - title: String! - actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) -} - -type Series implements Production { - title: String! - episodes: Int! - actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) -} - -type Actor { - name: String! -} ----- -|=== - - -=== Edge properties - -Edge properties in connections now exist within the field `properties` in `edges`: - -[cols="1,1"] -|=== -|Before | Now -a| -[source, graphql, indent=0] ----- -query ActedInConnection { - actors { - actedInConnection { - edges { - screenTime - } - } - } -} ----- -a| -[source, graphql, indent=0] ----- -query ActedInConnection { - actors { - actedInConnection { - edges { - properties { - screenTime - } - } - } - } -} ----- -|=== - - -=== `_on` filters deprecated - -`_on` filters for interfaces are no longer available in `where` and mutations. To filter by an implementing type, you need to use the new filter `typename_IN`: - - -[cols="1,1"] -|=== -|Before | Now -a| -[source, graphql, indent=0] ----- -query MyQuery { - actors( - where: { - actedInConnection_SINGLE: { node: { _on: { Movie: { } } } } - } - ) { - name - } -} ----- -a| -[source, graphql, indent=0] ----- -query MyQuery { - actors( - where: { - actedInConnection_SINGLE: { node: { typename_IN: [Movie] } } - } - ) { - name - } -} ----- -|=== - - -[NOTE] -==== -Using fields of a type in an interface operation is no longer supported. -==== - -==== Minimum NodeJS version - -With the deprecation of Node 16, the minimum supported NodeJS version is `18.0.0`. - -==== `experimental` flag - -The `experimental` flag is no longer available in the options of the `Neo4jGraphQL` class. \ No newline at end of file From d79c3a8ae02811f38dc2bfc0cd337c80ef75934d Mon Sep 17 00:00:00 2001 From: MacondoExpress Date: Mon, 14 Oct 2024 16:28:06 +0100 Subject: [PATCH 6/9] resovle merge conflict on authentication.adoc --- .../ROOT/pages/security/authentication.adoc | 39 +------------------ 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/modules/ROOT/pages/security/authentication.adoc b/modules/ROOT/pages/security/authentication.adoc index 26552c94..72f2bde6 100644 --- a/modules/ROOT/pages/security/authentication.adoc +++ b/modules/ROOT/pages/security/authentication.adoc @@ -9,44 +9,7 @@ Explicit authentication, configured with the `@authentication` directive, is onl Unauthenticated requests with queries requiring authentication never reach the database. ==== -Authentication can be configured for an entire type, for example, the type `User`: - -[source, graphql, indent=0] ----- -type User @authentication @node { - id: ID! - name: String! - password: String! -} ----- - -Authentication will thus be validated when any of the following operations are _attempted_: - -* *Create*: `createUsers` mutation, `create`, or `connectOrCreate` nested operation via a related type. -* *Read*: `users`, `usersConnection`, `usersAggregate` query, or access via related type. -* *Update*: `updateUsers` mutation or `update` nested operation via a related type. -* *Delete*: `deleteUsers` mutation or `delete` nested operation via a related type. -* *Create relationship*: `connect` or `connectOrCreate` nested operation via a related type. -* *Delete relationship*: `disconnect` nested operation via a related type. -* *Subscribe*: all subscription operations related to type `User`. - -Additionally, the directive can be configured on a per-field basis, for example: - -[source, graphql, indent=0] ----- -type User @node { - id: ID! - name: String! - password: String! @authentication -} ----- - -This will only be evaluated in the following circumstances: - -* The `password` field is set on either `create` or `update`. -* The `password` field is present in a selection set. - -=== Operations +== Operations Authentication can be configured to only be validated on certain operations: From c22e466299a476ef62884f8f9b228cb4545cc682 Mon Sep 17 00:00:00 2001 From: MacondoExpress Date: Wed, 16 Oct 2024 09:16:23 +0100 Subject: [PATCH 7/9] Apply suggestions from code review Co-authored-by: Richard Sill <156673635+rsill-neo4j@users.noreply.github.com> --- modules/ROOT/pages/directives/database-mapping.adoc | 8 ++++---- modules/ROOT/pages/migration/index.adoc | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/ROOT/pages/directives/database-mapping.adoc b/modules/ROOT/pages/directives/database-mapping.adoc index ad882406..648825b4 100644 --- a/modules/ROOT/pages/directives/database-mapping.adoc +++ b/modules/ROOT/pages/directives/database-mapping.adoc @@ -23,8 +23,8 @@ type Movie @node { [NOTE] ==== -In the current version, it's not required to specify every GraphQL type representing a Neo4j node with the `@node` directive. -In the future, types without the `@node` directive will no longer treated as Neo4j nodes. +In version 6.x, it's not required to specify every GraphQL type representing a Neo4j node with the `@node` directive. +In the future, types without the `@node` directive will no longer be treated as Neo4j nodes. ==== When not differently specified, the GraphQL type name is used as a label for the represented Neo4j node. It's possible to explicitly define the Neo4j node labels by using the argument `labels`: @@ -161,9 +161,9 @@ await startStandaloneServer(server, { == `@relationship` Relationships are represented by marking particular fields with a directive -- in this case, `@relationship`. -It defines the relationship type in the database, as well as which direction that relationship goes in. +It defines the relationship type in the database, as well as it's direction. -To add a second node type, "Actor", and connect the two together, you should do the following: +The following type definitions add a second node type, `Actor`, and connect `Movie` and `Actor` via the `ACTED_IN` relationship: [source, graphql, indent=0] ---- diff --git a/modules/ROOT/pages/migration/index.adoc b/modules/ROOT/pages/migration/index.adoc index cb768928..6f32fcb3 100644 --- a/modules/ROOT/pages/migration/index.adoc +++ b/modules/ROOT/pages/migration/index.adoc @@ -21,13 +21,13 @@ npm update @neo4j/graphql Here is a list of all the breaking changes from version 5.0.0 to 6.0.0. -== Deprecations and Warnings +== Deprecations and warnings -=== `@node` will be required to be explicitly defined +=== `@node` will have to be explicitly defined In the future, types without the `@node` directive will no longer be treated as Neo4j nodes. -In version 6.0.0, it's not required to specify every GraphQL type representing a Neo4j node with the `@node` directive, however it's recommended to do so and a warning is raised if not found. -There are genuine cases in which a `@node` should not be used e.g., for type returned by @cypher fields which are not Neo4j nodes. +In version 6.0.0, it's not required to specify every GraphQL type representing a Neo4j node with the `@node` directive, however it's recommended to do so and a warning is raised if you don't. +There are genuine cases in which a `@node` should not be used, e.g., for types returned by @cypher fields which are not Neo4j nodes. [cols="1,1"] |=== From a9d398f24043c5893d86e7a92b325a7efb2645ce Mon Sep 17 00:00:00 2001 From: MacondoExpress Date: Wed, 16 Oct 2024 09:29:27 +0100 Subject: [PATCH 8/9] rever filtering.adoc to 6.x and apply again --- .../pages/queries-aggregations/filtering.adoc | 107 +----------------- 1 file changed, 3 insertions(+), 104 deletions(-) diff --git a/modules/ROOT/pages/queries-aggregations/filtering.adoc b/modules/ROOT/pages/queries-aggregations/filtering.adoc index d4f9dd46..b5914e2a 100644 --- a/modules/ROOT/pages/queries-aggregations/filtering.adoc +++ b/modules/ROOT/pages/queries-aggregations/filtering.adoc @@ -103,108 +103,7 @@ query { Spatial types use numerical filtering differently and they also have additional options. See xref:filtering.adoc#_filtering_spatial_types[Filtering spatial types] for more information. -[source, javascript, indent=0] ----- -const { Neo4jGraphQL } = require("@neo4j/graphql"); -const neo4j = require("neo4j-driver"); - -const typeDefs = ` - type User @node { - name: String - } -`; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("username", "password") -); - -const features = { - filters: { - String: { - LT: true, - GT: true, - LTE: true, - GTE: true - } - } -}; - -const neoSchema = new Neo4jGraphQL({ features, typeDefs, driver }); ----- - -== RegEx matching - -The filter `_MATCHES` is also available for comparison of `String` and `ID` types. -It accepts RegEx strings as an argument and returns any matches. - - -Note that RegEx matching filters are **disabled by default**. -This is because, on an unprotected API, they could potentially be used to execute a https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS[ReDoS attack^] against the backing Neo4j database. - -If you want to enable them, set the features configuration object for each: - -[source, javascript, indent=0] ----- -const features = { - filters: { - String: { - MATCHES: true, - } - } -}; - -const neoSchema = new Neo4jGraphQL({ features, typeDefs, driver }); ----- - -For `ID`: - - -[source, javascript, indent=0] ----- -const features = { - filters: { - String: { - ID: true, - } - } -}; - -const neoSchema = new Neo4jGraphQL({ features, typeDefs, driver }); ----- - -For both `String` and `ID`: - - -[source, javascript, indent=0] ----- -const features = { - filters: { - String: { - MATCHES: true, - }, - ID: { - MATCHES: true, - } - } -}; - -const neoSchema = new Neo4jGraphQL({ features, typeDefs, driver }); ----- - -== Array comparison - -The following operator is available on non-array fields, and accepts an array argument: - -* `_IN` - -Conversely, the following operator is available on array fields, and accepts a single argument: - -* `_INCLUDES` - -These operators are available for all types apart from `Boolean`. - -== Filtering spatial types +==== Spatial type filtering Both the `Point` and the `CartesianPoint` types use xref::queries-aggregations/filtering.adoc#_numerical_operators[numerical operators] and have an additional `_DISTANCE` filter. Here is a list of what each filter does for the two types: @@ -298,7 +197,7 @@ const { Neo4jGraphQL } = require("@neo4j/graphql"); const neo4j = require("neo4j-driver"); const typeDefs = ` - type User { + type User @node { name: String } `; @@ -725,4 +624,4 @@ query EventsAggregate { | - | - -|=== +|=== \ No newline at end of file From 9e5fb48b1cda0bad12b6ec0e2d235ecc9dcf373d Mon Sep 17 00:00:00 2001 From: Richard Sill <156673635+rsill-neo4j@users.noreply.github.com> Date: Wed, 16 Oct 2024 16:37:51 +0200 Subject: [PATCH 9/9] Update modules/ROOT/pages/directives/database-mapping.adoc --- modules/ROOT/pages/directives/database-mapping.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/pages/directives/database-mapping.adoc b/modules/ROOT/pages/directives/database-mapping.adoc index 648825b4..39dcd2bb 100644 --- a/modules/ROOT/pages/directives/database-mapping.adoc +++ b/modules/ROOT/pages/directives/database-mapping.adoc @@ -161,7 +161,7 @@ await startStandaloneServer(server, { == `@relationship` Relationships are represented by marking particular fields with a directive -- in this case, `@relationship`. -It defines the relationship type in the database, as well as it's direction. +It defines the relationship type in the database, as well as its direction. The following type definitions add a second node type, `Actor`, and connect `Movie` and `Actor` via the `ACTED_IN` relationship: