From 57aeccafc3396b75db0120bb956e569fd47d3862 Mon Sep 17 00:00:00 2001 From: Neil Dewhurst Date: Tue, 19 Sep 2023 08:50:50 +0100 Subject: [PATCH 01/28] Update docs-pr.yml Add dev branch --- .github/workflows/docs-pr.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yml index 72398201..02bdd80d 100644 --- a/.github/workflows/docs-pr.yml +++ b/.github/workflows/docs-pr.yml @@ -8,6 +8,7 @@ on: pull_request: branches: - "main" + - "dev" jobs: From f2fdec53c7fa8eb504c4f72dbc54fe75e8623e66 Mon Sep 17 00:00:00 2001 From: Neil Dewhurst Date: Tue, 19 Sep 2023 08:51:14 +0100 Subject: [PATCH 02/28] Update docs-teardown.yml Add dev branch --- .github/workflows/docs-teardown.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docs-teardown.yml b/.github/workflows/docs-teardown.yml index 5af4f836..a4cdd4bf 100644 --- a/.github/workflows/docs-teardown.yml +++ b/.github/workflows/docs-teardown.yml @@ -5,6 +5,7 @@ on: pull_request_target: branches: - "main" + - "dev" types: - closed From 6ffa5c798bd12b9dc67fb4db4ae88b7f27c52bc6 Mon Sep 17 00:00:00 2001 From: Darrell Warde <8117355+darrellwarde@users.noreply.github.com> Date: Thu, 21 Sep 2023 13:06:08 +0100 Subject: [PATCH 03/28] Update content-nav.adoc (#37) Fix location of driver configuration page --- modules/ROOT/content-nav.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index 83dbafa8..79d0e923 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -72,7 +72,7 @@ *** xref:ogm/type-generation.adoc[] *** xref:ogm/reference.adoc[] -*** xref:driver-configuration.adoc[] +** xref:driver-configuration.adoc[] ** Frameworks and integrations *** xref:integrations/apollo-federation.adoc[] From 41e8a1c87d6c849d06f1f713abebec5fe3f593bb Mon Sep 17 00:00:00 2001 From: Lidia Zuin <102308961+lidiazuin@users.noreply.github.com> Date: Mon, 25 Sep 2023 17:53:35 +0200 Subject: [PATCH 04/28] Tackling user feedback (#35) * Tackling user feedback * card 4800, adding example for arguments in update queries * updating cypher query for fulltext index creation * bumping to test surge * revert * revert * Update modules/ROOT/pages/type-definitions/directives/indexes-and-constraints.adoc Co-authored-by: Darrell Warde <8117355+darrellwarde@users.noreply.github.com> * reverting changes on the api reference page * update * Update modules/ROOT/pages/ogm/reference.adoc Co-authored-by: Darrell Warde <8117355+darrellwarde@users.noreply.github.com> --------- Co-authored-by: Darrell Warde <8117355+darrellwarde@users.noreply.github.com> --- modules/ROOT/pages/queries-aggregations/queries.adoc | 3 ++- .../type-definitions/directives/indexes-and-constraints.adoc | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/ROOT/pages/queries-aggregations/queries.adoc b/modules/ROOT/pages/queries-aggregations/queries.adoc index 024250da..35fc6ccd 100644 --- a/modules/ROOT/pages/queries-aggregations/queries.adoc +++ b/modules/ROOT/pages/queries-aggregations/queries.adoc @@ -52,7 +52,8 @@ query { } ---- -In addition, undirected relationships can also be used in the same fashion with connections: +In addition, undirected relationships can also be used in the same fashion with connections. +For instance, this query is asking for a list of users and their friends' names with an undirected friendship connection: [source, graphql, indent=0] ---- diff --git a/modules/ROOT/pages/type-definitions/directives/indexes-and-constraints.adoc b/modules/ROOT/pages/type-definitions/directives/indexes-and-constraints.adoc index db903bb7..ec9b3bc5 100644 --- a/modules/ROOT/pages/type-definitions/directives/indexes-and-constraints.adoc +++ b/modules/ROOT/pages/type-definitions/directives/indexes-and-constraints.adoc @@ -104,7 +104,7 @@ When you run xref::/type-definitions/directives/indexes-and-constraints.adoc#_as [source, cypher, indent=0] ---- -CALL db.index.fulltext.createNodeIndex("ProductName", ["Product"], ["name"]) +CREATE FULLTEXT INDEX ProductName FOR (n:Product) ON EACH [n.name] ---- === Usage From b026e20e4de2f5c63fe14f214cd71ed91d64e19c Mon Sep 17 00:00:00 2001 From: Darrell Warde <8117355+darrellwarde@users.noreply.github.com> Date: Tue, 26 Sep 2023 13:08:11 +0100 Subject: [PATCH 05/28] Fix scalar description placeholders (#38) --- modules/ROOT/pages/type-definitions/types/scalar.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/ROOT/pages/type-definitions/types/scalar.adoc b/modules/ROOT/pages/type-definitions/types/scalar.adoc index 67315112..c3f40b20 100644 --- a/modules/ROOT/pages/type-definitions/types/scalar.adoc +++ b/modules/ROOT/pages/type-definitions/types/scalar.adoc @@ -43,7 +43,7 @@ query { ---- | `Float` -| Placeholder text to make the table prettier. +| Represents signed double‐precision fractional values. a| [source, graphql, indent=0] ---- @@ -63,7 +63,7 @@ type Product { ---- | `Boolean` -| Placeholder text to make the table prettier. +| Represents `true` or `false`. a| [source, graphql, indent=0] ---- @@ -80,4 +80,4 @@ type Product { id: ID! } ---- -|=== \ No newline at end of file +|=== From 0987f5e1f047c828eb3ea62f5a478c72fb322c4b Mon Sep 17 00:00:00 2001 From: Neil Dewhurst Date: Tue, 26 Sep 2023 18:49:49 +0100 Subject: [PATCH 06/28] Use aliases-redirects extension (#40) --- package.json | 1 + publish.yml | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/package.json b/package.json index 14fbecea..7e05b22f 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@neo4j-antora/antora-add-notes": "^0.1.6", "@neo4j-antora/antora-modify-sitemaps": "^0.4.3", "@neo4j-antora/antora-page-roles": "^0.3.1", + "@neo4j-antora/aliases-redirects": "^0.2.1", "@neo4j-antora/antora-table-footnotes": "^0.3.2", "@neo4j-documentation/macros": "^1.0.2", "@neo4j-documentation/remote-include": "^1.0.0" diff --git a/publish.yml b/publish.yml index e9d0695b..3a563497 100644 --- a/publish.yml +++ b/publish.yml @@ -28,6 +28,11 @@ antora: sitemap_version: '4' sitemap_loc_version: 'current' move_sitemaps_to_components: true + - require: "@neo4j-antora/aliases-redirects" + redirect_format: neo4j + redirect_map: + - from: '4' + to: 'current' asciidoc: extensions: From 57b00e92970d5ba8c4f7f258d20699e357517e85 Mon Sep 17 00:00:00 2001 From: Lidia Zuin <102308961+lidiazuin@users.noreply.github.com> Date: Tue, 3 Oct 2023 11:36:02 +0200 Subject: [PATCH 07/28] Fixing typo in code example (#45) --- modules/ROOT/pages/subscriptions/getting-started.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/pages/subscriptions/getting-started.adoc b/modules/ROOT/pages/subscriptions/getting-started.adoc index d091fdc6..541df990 100644 --- a/modules/ROOT/pages/subscriptions/getting-started.adoc +++ b/modules/ROOT/pages/subscriptions/getting-started.adoc @@ -123,7 +123,7 @@ async function main() { const PORT = 4000; httpServer.listen(PORT, () => { - console.log(`Server is now running on http://localhost:${PORT}/grahpql`); + console.log(`Server is now running on http://localhost:${PORT}/graphql`); }); } From 166b72c699a36d47be3fa12b78df8dd3fa86f993 Mon Sep 17 00:00:00 2001 From: Lidia Zuin <102308961+lidiazuin@users.noreply.github.com> Date: Thu, 5 Oct 2023 15:21:32 +0200 Subject: [PATCH 08/28] Update to the migrations guide (#43) * Update to the migrations guide * fixing conflicts * Apply suggestions from code review Co-authored-by: MacondoExpress * changes after review --------- Co-authored-by: MacondoExpress --- modules/ROOT/content-nav.adoc | 10 +- .../{v4-migration => }/authorization.adoc | 41 +- modules/ROOT/pages/migration/index.adoc | 1167 ++++++++++------- .../migration/{v4-migration => }/ogm.adoc | 9 +- .../ROOT/pages/migration/v2-migration.adoc | 606 --------- .../ROOT/pages/migration/v3-migration.adoc | 253 ---- .../pages/migration/v4-migration/index.adoc | 1015 -------------- .../pages/reference/api-reference/index.adoc | 3 +- .../reference/api-reference/neo4jgraphql.adoc | 2 +- .../pages/reference/api-reference/ogm.adoc | 2 +- .../pages/reference/directives/cypher.adoc | 229 ---- .../reference/directives/default-values.adoc | 96 -- .../pages/reference/directives/index.adoc | 161 --- .../type-configuration.adoc | 156 --- .../type-definitions/interfaces.adoc | 135 -- modules/ROOT/pages/subscriptions/events.adoc | 2 +- 16 files changed, 757 insertions(+), 3130 deletions(-) rename modules/ROOT/pages/migration/{v4-migration => }/authorization.adoc (80%) rename modules/ROOT/pages/migration/{v4-migration => }/ogm.adoc (70%) delete mode 100644 modules/ROOT/pages/migration/v2-migration.adoc delete mode 100644 modules/ROOT/pages/migration/v3-migration.adoc delete mode 100644 modules/ROOT/pages/migration/v4-migration/index.adoc delete mode 100644 modules/ROOT/pages/reference/directives/cypher.adoc delete mode 100644 modules/ROOT/pages/reference/directives/default-values.adoc delete mode 100644 modules/ROOT/pages/reference/directives/index.adoc delete mode 100644 modules/ROOT/pages/reference/directives/schema-configuration/type-configuration.adoc delete mode 100644 modules/ROOT/pages/reference/type-definitions/interfaces.adoc diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index 79d0e923..3b162d25 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -56,13 +56,9 @@ ** xref:introspector.adoc[Introspector] -** Migration Guides -*** xref:migration/index.adoc[Migration from `neo4j-graphql-js`] -*** xref:migration/v2-migration.adoc[] -*** xref:migration/v3-migration.adoc[] -*** xref:migration/v4-migration/index.adoc[] -**** xref:migration/v4-migration/authorization.adoc[] -**** xref:migration/v4-migration/ogm.adoc[] +** xref:migration/index.adoc[Migration guide] +*** xref:migration/authorization.adoc[] +*** xref:migration/ogm.adoc[] ** xref:ogm/index.adoc[] *** xref:ogm/installation.adoc[] diff --git a/modules/ROOT/pages/migration/v4-migration/authorization.adoc b/modules/ROOT/pages/migration/authorization.adoc similarity index 80% rename from modules/ROOT/pages/migration/v4-migration/authorization.adoc rename to modules/ROOT/pages/migration/authorization.adoc index 415218db..561b6f04 100644 --- a/modules/ROOT/pages/migration/v4-migration/authorization.adoc +++ b/modules/ROOT/pages/migration/authorization.adoc @@ -1,12 +1,14 @@ = Authentication and Authorization -:page-aliases: auth/global-authentication.adoc +:description: This page describes the changes in authentication and authorization features in version 4.0.0 of the Neo4j GraphQL Library. +:page-aliases: auth/global-authentication.adoc, migration/v4-migration/authorization.adoc -The largest breaking change in version 4.0.0 is the removal of the `@auth` directive, which requires a migration to the new `@authentication`, `@authorization` and `@subscriptionsAuthorization` directives. +The largest breaking change in version 4.0.0 is the removal of the `@auth` directive, which requires a migration to the new `@authentication`, `@authorization`, and `@subscriptionsAuthorization` directives. == Instantiation -Whilst the `@auth` directive required the installation of an additional plugin package, the functionality for the new directives is built directly into the library. -You should uninstall the previous plugin: +While the `@auth` directive required the installation of an additional plugin package, the functionality for the new directives is now built directly into the library. + +To start using it, you should uninstall the previous plugin: [source, bash, indent=0] ---- @@ -15,7 +17,7 @@ npm uninstall @neo4j/graphql-plugin-auth === Symmetric secret -Given an example of instantiation using a symmetric secret with the plugin: +Given this example of instantiation using a symmetric secret with the plugin: [source, typescript, indent=0] ---- @@ -112,8 +114,9 @@ This is to acknowledge the fact that there are a variety of servers which don't === `rolesPath` The `rolesPath` argument was used to configure a custom path for the "roles" claim in the JWT structure. -This configuration has now been moved into the type definitions themselves. -So given a previous instantiation: +This configuration has now been moved into the type definitions themselves. + +Given a previous instantiation: [source, typescript, indent=0] ---- @@ -139,11 +142,11 @@ type JWT @jwt { The type name itself can be anything, as long as it is decorated by `@jwt`. -Whilst there is more setup required in this version, the strongly typed nature of the definition means there is significantly more powerful filtering options in version 4.0.0. +Despite the extra setup steps required in 4.0.0, the strongly typed nature of the definition means there is now significantly more powerful filtering options. == Global authentication -Global authentication was previously configured in the auth plugin consructor, for instance: +Global authentication was previously configured in the auth plugin constructor, for instance: [source, typescript, indent=0] ---- @@ -187,13 +190,11 @@ type User @authorization(validate: [{ when: [BEFORE], where: { node: { id: "$jwt } ---- -Note that `allow` is no longer a discrete rule, but configured by a `when` argument which is an array accepting the values `BEFORE` and `AFTER`. - -It is expected that users will quite rarely need to specify this argument as it defaults to both, and most users will want to validate a node property both before and after each operation. +Note that `allow` is no longer a discrete rule, but configured by a `when` argument, which is an array accepting the values `BEFORE` and `AFTER`. === `bind` -Given an `bind` rule, which checks the `id` field of a `User` against the JWT subject _after_ any operation: +Given a `bind` rule, which checks the `id` field of a `User` against the JWT subject _after_ any operation: [source, graphql, indent=0] ---- @@ -213,14 +214,12 @@ type User @authorization(validate: [{ when: [AFTER], where: { node: { id: "$jwt. Note that `bind` is no longer a discrete rule, but configured by a `when` argument which is an array accepting values `BEFORE` and `AFTER`. -It is expected that users will quite rarely need to specify this argument as it defaults to both, and most users will want to validate a node property both before and after each operation. - === `isAuthenticated` [WARNING] ==== There isn't a direct replacement for the `isAuthenticated` argument. -Please https://github.com/neo4j/graphql/issues/new/choose[raise a feature request] if this is blocking migration. +https://github.com/neo4j/graphql/issues/new/choose[Raise a feature request] if this is blocking migration. ==== Given a previous type definition, which required authentication for any operation on the type `User`: @@ -258,7 +257,7 @@ This happens _before_ the database execution in order to restrict database acces === `roles` -For these examples, the following type is required in the type definitions: +For these examples, the following is required in the type definitions: [source, graphql, indent=0] ---- @@ -267,7 +266,7 @@ type JWT @jwt { } ---- -Given the following type definition, which requires a user to have the "admin" role to perform any operation on the type `User`: +Given the following type definition, which requires a user to have the `admin` role to perform any operation on the type `User`: [source, graphql, indent=0] ---- @@ -287,10 +286,10 @@ type User @authorization(validate: [{ where: { jwt: { roles_INCLUDES: "admin" } The following changes were made for this migration: -* A `validate` rule has been used, which will throw an error without the role as per the `roles` argument in the `@auth` directive. +* If a `validate` rule has been used, it throws an error without the role as per the `roles` argument in the `@auth` directive. This can alternatively be a `filter` rule to just return zero results if a user does not have the required role. * `roles` has become `roles_INCLUDES`, because the xref::queries-aggregations/filtering.adoc[full filtering capabilities of the library] can now be used within the `@authorization` directive. -* `roles` is no longer a top-level rule field, but nested within `where` under `jwt`. +* `roles` is no longer a top-level rule field, but nested within `where`, under `jwt`. Any number of JWT claims can now be compared against, if configured within the type decorated with `@jwt`. === `where` @@ -304,7 +303,7 @@ type User @auth(rules: [{ where: { id: "$jwt.sub" } }]) { } ---- -Now the `@authorization` directive must be: +And now the `@authorization` directive must be: [source, graphql, indent=0] ---- diff --git a/modules/ROOT/pages/migration/index.adoc b/modules/ROOT/pages/migration/index.adoc index 25707e94..72b159bb 100644 --- a/modules/ROOT/pages/migration/index.adoc +++ b/modules/ROOT/pages/migration/index.adoc @@ -1,729 +1,1012 @@ -[[migration-guide]] -= Migration from `neo4j-graphql-js` -:page-aliases: guides/index.adoc, guides/migration-guide/index.adoc, guides/migration-guide/server.adoc, guides/migration-guide/type-definitions.adoc, guides/migration-guide/mutations.adoc +[[v4-migration]] +:description: This page lists the breaking changes from version 3.0.0 to 4.0.0 and describes how to update. +:page-aliases: guides/v4-migration/index.adoc , migration/v4-migration/index.adoc += Migration to 4.0.0 +This page lists all breaking changes from the Neo4j GraphQL Library version 3.x to 4.x and how to update it. -`@neo4j/graphql` was never intended to be a drop-in replacement for `neo4j-graphql-js`. However, simple applications should have a fairly trivial migration process. +== How to update -== How to Upgrade - -You need to uninstall the old library and install the new one (and its peer dependencies) using npm or your package manager of choice: +To update your Neo4j GraphQL Library, use npm or the package manager of choice: [source, bash, indent=0] ---- -npm uninstall neo4j-graphql-js -npm install @neo4j/graphql graphql neo4j-driver +npm update @neo4j/graphql ---- -Subscriptions are not supported at this stage. +== Breaking changes -[[migration-guide-server]] -== Server +Here is a list of all the breaking changes from version 3.0.0 to 4.0.0. -[[migration-guide-server-schema-generation]] -=== Schema Generation +=== `IExecutableSchemaDefinition` -In your server codebase, the process of creating an executable schema has changed. For a simple application, what used to be: +If you were passing any arguments from https://the-guild.dev/graphql/tools/docs/api/interfaces/schema_src.iexecutableschemadefinition[`IExecutableSchemaDefinition`] into the library other than `typeDefs` and `resolvers`, these are no longer supported. -[source, javascript, indent=0] ----- -const { makeAugmentedSchema } = require("neo4j-graphql-js"); +=== `config.enableDebug` -const typeDefs = require("./type-definitions"); +The programmatic toggle for debug logging has been moved from `config.enableDebug` to simply `debug`. -const schema = makeAugmentedSchema({ typeDefs }); ----- +[cols="1,1"] +|=== +|Before | Now -Has become: +a| +[source, javascript, indent=0] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const neo4j = require("neo4j-driver"); +const { ApolloServer } = require("apollo-server"); +const typeDefs = ` + type Movie { + title: String! + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const neoSchema = new Neo4jGraphQL({ + typeDefs, + driver, + config: { + enableDebug: true, + } +}); +---- +a| [source, javascript, indent=0] ---- const { Neo4jGraphQL } = require("@neo4j/graphql"); +const neo4j = require("neo4j-driver"); +const { ApolloServer } = require("apollo-server"); -const typeDefs = require("./type-definitions"); +const typeDefs = ` + type Movie { + title: String! + } +`; -const neo4jGraphQL = new Neo4jGraphQL({ typeDefs }); +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); -const schema = await neo4jGraphQL.getSchema(); +const neoSchema = new Neo4jGraphQL({ + typeDefs, + driver, + debug: true, +}); ---- +|=== -Additionally, the `Neo4jGraphQL` constructor allows you to pass in a driver instance instead of passing one in the context of each request. - -=== Schema Configuration +=== `driverConfig` -The `neo4j-graphql-js` library would allow a user to configure the GraphQL schema via the `makeAugmentedSchema()` function. +Session configuration is now available only in the context under the `sessionConfig` key. +Additionally, the `bookmarks` key has been removed as it is no longer needed with the bookmark manager of the newer driver. -For example, to exclude all queries and all mutations: +[cols="1,1"] +|=== +|Before | Now +a| [source, javascript, indent=0] ---- -const schema = makeAugmentedSchema({ - typeDefs, - config: { - query: false, // exclude all queries - mutation: false // exclude all mutations - } -}); ----- +import { ApolloServer } from '@apollo/server'; +import { startStandaloneServer } from '@apollo/server/standalone'; +import { Neo4jGraphQL } from "@neo4j/graphql"; +import neo4j from "neo4j-driver"; -Or to exclude queries for a specific type like so: - -[source, javascript, indent=0] ----- -const schema = makeAugmentedSchema({ - typeDefs, - config: { - query: { - exclude: ["NameOfTypeToExclude"] // exclude query for type NameOfTypeToExclude +const typeDefs = `#graphql + type User { + name: String + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const neoSchema = new Neo4jGraphQL({ + typeDefs, + config: { + driverConfig: { + database: "different-db" + }, }, - mutation: false // exclude all mutations - } +}) + +const server = new ApolloServer({ + schema: await neoSchema.getSchema(), }); ----- -To achieve the same behaviour with the new `@neo4j/graphql` library, the GraphQL schema has to be extended instead. Note, no additional configuration or parameters need to be passed to the `getSchema()` function. +await startStandaloneServer(server, { + context: async ({ req }) => ({ req }), +}); -To exclude all mutations for a specific type: -[source, graphql, indent=0] ---- -type NameOfType @exclude(operations: [CREATE, UPDATE, DELETE]) { - name: String -} +a| +[source, javascript, indent=0] ---- +import { ApolloServer } from '@apollo/server'; +import { startStandaloneServer } from '@apollo/server/standalone'; +import { Neo4jGraphQL } from "@neo4j/graphql"; +import neo4j from "neo4j-driver"; -Or to exclude all queries and all mutations for a specific type like so: - -[source, graphql, indent=0] ----- -type NameOfTypeToExclude @exclude { - name: String -} ----- -For more information regarding the above used `@exclude` directive, see xref::/schema-configuration/type-configuration.adoc#_exclude_deprecated[`@exclude`] +const typeDefs = `#graphql + type User { + name: String + } +`; -=== Database Configuration +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); -==== Driver +const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); -Once a schema has been generated, it can be passed into a GraphQL server instance with the driver in the context, identical to using the old library. For example, using Apollo Server and using the schema generated above: +const server = new ApolloServer({ + schema: await neoSchema.getSchema(), +}); -[source, javascript, indent=0] +await startStandaloneServer(server, { + context: async ({ req }) => ({ sessionConfig: { database: "my-database" }}), +}); ---- -const { ApolloServer } = require("apollo-server"); +|=== -const driver = require("./driver"); -const schema = require("./schema"); +=== `config.enableRegex` -const server = new ApolloServer({ schema, context: { driver } }); +This has been replaced by `MATCHES` in features.filters. +With this change comes more granularity in the feature configuration. +You can now enable the `MATCHES` filter on `String` and `ID` fields separately: -server.listen().then(({ url }) => { - console.log(`@neo4j/graphql API ready at ${url}`); +[cols="1,1"] +|=== +|Before | After + +a| +[source, javascript, indent=0] +---- +neoSchema = new Neo4jGraphQL({ + typeDefs, + config: { + enableRegex: true + } +}); +---- +a| +[source, javascript, indent=0] +---- +neoSchema = new Neo4jGraphQL({ + typeDefs, + features: { + filters: { + String: { + MATCHES: true, + }, + ID: { + MATCHES: true, + }, + }, + }, }); ---- +|=== -==== Multiple Databases +=== `queryOptions` -Multiple databases were supported in `neo4j-graphql-js` by passing a context key `neo4jDatabase` with the name of a database, for example for database called "sanmateo": +If you had a need to pass in Cypher query options for query tuning, this interface has been changed. +The config option `queryOptions` has now become `cypherQueryOptions` inside the context function, and it now accepts simple strings instead of enums. +This change reflects the fact that the Cypher query options are set on a per-request basis. +[cols="1,1"] +|=== +|Before | Now + +a| [source, javascript, indent=0] ---- +const { Neo4jGraphQL, CypherRuntime } = require("@neo4j/graphql"); const { ApolloServer } = require("apollo-server"); -const driver = require("./driver"); -const schema = require("./schema"); +const typeDefs = ` + type Movie { + title: String! + } +`; + +const neoSchema = new Neo4jGraphQL({ + typeDefs, + config: { + queryOptions: { + runtime: CypherRuntime.INTERPRETED, + }, + }, +}); -const server = new ApolloServer({ schema, context: { driver, neo4jDatabase: "sanmateo" } }); +const server = new ApolloServer({ + schema: await neoSchema.getSchema(), +}); -server.listen().then(({ url }) => { - console.log(`@neo4j/graphql API ready at ${url}`); +await startStandaloneServer(server, { + context: async ({ req }) => ({ req }), }); ---- -In `@neo4j/graphql`, this has now become: - +a| [source, javascript, indent=0] ---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); const { ApolloServer } = require("apollo-server"); -const driver = require("./driver"); -const schema = require("./schema"); +const typeDefs = ` + type Movie { + title: String! + } +`; -const server = new ApolloServer({ schema, context: { driver, driverConfig: { database: "sanmateo" } } }); +const neoSchema = new Neo4jGraphQL({ + typeDefs, +}); -server.listen().then(({ url }) => { - console.log(`@neo4j/graphql API ready at ${url}`); +const server = new ApolloServer({ + schema: await neoSchema.getSchema(), }); ----- -Database bookmarks are also supported. See xref::driver-configuration.adoc[Driver Configuration] for more information. +await startStandaloneServer(server, { + context: async ({ req }) => ({ cypherQueryOptions: { runtime: "interpreted" }}), +}); +---- +|=== -[[migration-guide-type-definitions]] -== Type Definitions +=== `skipValidateTypeDefs` -This page will walk through what needs to change in your type definitions before you can pass them into `@neo4j/graphql`. +The argument has been moved to the top-level of the constructor input and renamed `validate`, which defaults to `true`. +If you started using the `config.startupValidation` option, this has also been rolled into the same `validate` setting for simplicity. -=== Directives +Likewise, the `resolvers` option is now just a warning, and `noDuplicateRelationshipFields` is now a mandatory check rolled into `validate`. -Both `neo4j-graphql-js` and `@neo4j/graphql` are highly driven by GraphQL directives. Each heading in this section will address how/if one or many directives available in `neo4j-graphql-js` can be migrated to `@neo4j/graphql`. +Here is an example query of how it looks now: -==== `@relation` +[cols="1,1"] +|=== +|Before | After -Migrating this directive is trivial: +a| +[source, javascript, indent=0] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, + config: { + skipValidateTypeDefs: true, + }, +}) +---- +a| +[source, javascript, indent=0] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, + validate: false, +}) +---- +|=== -1. Rename `@relation` to `@relationship` -2. Rename the argument `name` to `type` +=== `@cypher` -For example, `@relation(name: "ACTED_IN", direction: OUT)` becomes `@relationship(type: "ACTED_IN", direction: OUT)`. +The default behavior of the `@cypher` directive regarding the translation has changed. +Instead of using https://neo4j.com/labs/apoc/4.0/overview/apoc.cypher/apoc.cypher.runFirstColumnMany/[apoc.cypher.runFirstColumnMany], it directly wraps the query within a `CALL { }` subquery. -See xref::/type-definitions/types/relationships.adoc[Relationships] for more information on relationships in `@neo4j/graphql`. +This update has proven to be more performant for the same queries, however, it may lead to unexpected changes, mainly when using Neo4j 5.x, where the subqueries need to be _aliased_. -==== Relationship Properties +On top of that, to improve performance, it is recommended to pass the returned alias in the property `columnName`, to ensure the subquery is properly integrated into the larger query. -If for instance using `neo4j-graphql-js`, you have the following type definitions defining an `ACTED_IN` relationship with a `roles` property: +For example, the GraphQL query: [source, graphql, indent=0] ---- -type Actor { - movies: [ActedIn!]! -} - -type Movie { - actors: [ActedIn!]! +type query { + test: String! @cypher(statement: "RETURN 'hello'") } +---- -type ActedIn @relation(name: "ACTED_IN") { - from: Actor - to: Movie - roles: [String!] +Would be translated to: +[source,cypher, indent=0] +---- +CALL { + RETURN 'hello' } +WITH 'hello' AS this +RETURN this ---- -This will need to be refactored to the following in the new library: +Which is invalid in Neo4j 5.x. +To fix it, ensure the `RETURN` elements are aliased: [source, graphql, indent=0] ---- -type Actor { - movies: [Movie!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: OUT) +type query { + test: String! @cypher(statement: "RETURN 'hello' as result") } +---- -type Movie { - actors: [Actor!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN) -} +Another way to use this update is through an experimental option with the `columnName` flag in the `@cypher` directive: -interface ActedIn @relationshipProperties { - roles: [String!] +[source, graphql, indent=0] +---- +type query { + test: String! @cypher(statement: "RETURN 'hello' as result", columnName: "result") } ---- -Note the following changes to the `ActedIn` type: - -* Changed from `type` to `interface` -* Removed `@relation` directive -* Removed `from` and `to` fields - -And note the following changes to the two node types: +Note that escaping strings are no longer needed in Neo4j GraphQL 4.0.0. -* Relationship field types changed from the relationship type to the neighbouring node type -* Normal `@relationship` directive added to each relationship field, with an additional `properties` argument pointing to the relationship properties interface +=== `@fulltext` -=== `@cypher` - -No change. See xref::/type-definitions/directives/cypher.adoc[`@cypher` directive] for more details on this directive in `@neo4j/graphql`. - -==== `@neo4j_ignore` +In version 4.0.0, a number of improvements have been made to full-text queries. +These include the ability to return the full-text score, filter by the score and sorting by the score. +However, these improvements required a number of breaking changes. -`@neo4j/graphql` offers two directives for skipping autogeneration for specified types/fields: +==== Full-text queries -* xref::/schema-configuration/type-configuration.adoc#_exclude_deprecated[`@exclude`]: Skip generation of specified Query/Mutation fields for an object type -* xref::custom-resolvers.adoc#custom-resolver-directive[`@customResolver`]: Ignore a field, which will need custom logic for resolution +Full-text queries now need to be performed using a top-level query, instead of being performed using an argument on a node query. -==== `@isAuthenticated`, `@hasRole` and `@hasScope` +As a result, the following query is now invalid: -Will require significant migration, but will be worth the effort! See xref::authentication-and-authorization/index.adoc[Authentication and Authorization]. - -==== `@additionalLabels` +[source, graphql, indent=0] +---- +query { + movies(fulltext: { movieTitleIndex: { phrase: "Some Title" } }) { + title + } +} +---- -Not supported at this time. +The new top-level queries can be used to return the full-text score, which indicates the confidence of a match, as well as the nodes that have been matched. +They now accept the following arguments: -==== `@id` +* `phrase`: specifies the string to search for in the full-text index. +* `where`: accepts a min/max score as well as the normal filters available on a node. +* `sort: used to sort using the score and node attributes. +* `limit`: used to limit the number of results to the given integer. +* `offset`: used to offset by the given number of results. -There is an equivalent directive in the new library, but it does not work using database constraints as per the old library. See xref::/type-definitions/directives/autogeneration.adoc#type-definitions-autogeneration-id[`@id`]. +This means that, for the following type definition: -==== `@unique`, `@index` and `@search` +[source, graphql, indent=0] +---- +type Movie @fulltext(indexes: [{ indexName: "MovieTitle", fields: ["title"] }]) { # Note that indexName is the new name for the name argument. More about this below. + title: String! +} +---- -These all relate to database indexes and constraints, which are not currently supported by `@neo4j/graphql`. +The following top-level query and type definitions would be generated by the library: -=== Types +[source, graphql, indent=0] +---- +type Query { + movieFulltextMovieTitle(phrase: String!, where: MovieFulltextWhere, sort: [MovieFulltextSort!], limit: Int, offset: Int): [MovieFulltextResult!]! +} -==== Scalar Types +"""The result of a fulltext search on an index of Movie""" +type MovieFulltextResult { + score: Float + movies: Movie +} -Supported as you would expect, with additional xref::/type-definitions/types/scalar.adoc[`BigInt`] support for 64 bit integers. +"""The input for filtering a fulltext query on an index of Movie""" +input MovieFulltextWhere { + score: FloatWhere + movie: MovieWhere +} -==== Temporal Types (`DateTime`, `Date`) +"""The input for sorting a fulltext query on an index of Movie""" +input MovieFulltextSort { + score: SortDirection + movie: MovieSort +} -Temporal Types have been massively simplified in `@neo4j/graphql`, down to `DateTime` and `Date`, which use ISO 8601 and "yyyy-mm-dd" strings respectively for parsing and serialization. +"""The input for filtering the score of a fulltext search""" +input FloatWhere { + min: Float + max: Float +} +---- -In terms of migrating from the old library, the `formatted` field of the old `DateTime` type now becomes the value itself. For example, used in a query: +This query can then be used to perform a full-text query: [source, graphql, indent=0] ---- -{ - Movie(released: { formatted: "1992-10-09T00:00:00Z" }) { - title +query { + movieFulltextMovieTitle( + phrase: "Full Metal Jacket", + where: { score: min: 0.4 }, + sort: [{ movie: { title: ASC } }], + limit: 5, + offset: 10 + ) { + score + movies { + title + } } } ---- -Has become: +And thus return results in the following format: -[source, graphql, indent=0] +[source, json, indent=0] ---- { - Movie(released: "1992-10-09T00:00:00Z") { - title + "data": { + "movieFulltextMovieTitle": [ + { + "score": 0.44524085521698, + "movie": { + "title": "Full Moon High" + } + }, + { + "score": 1.411118507385254, + "movie": { + "title": "Full Metal Jacket" + } + } + ] } } ---- -Due to the move to ISO 8601 strings, input types are no longer necessary for temporal instances, so `_Neo4jDateTimeInput` has become `DateTime` and `_Neo4jDateInput` has become `Date` for input. - -See xref::/type-definitions/types/temporal.adoc[Temporal Types]. - -==== Spatial Types - -The single type in `neo4j-graphql-js`, `Point`, has been split out into two types: - -* xref::/type-definitions/types/spatial.adoc#_point[`Point`] -* xref::/type-definitions/types/spatial.adoc#_cartesianpoint[`CartesianPoint`] - -Correspondingly, `_Neo4jPointInput` has also been split out into two input types: - -* `PointInput` -* `CartesianPointInput` - -Using them in Queries and Mutations should feel remarkably similar. - -==== Interface Types +==== Argument changes -Supported, queryable using inline fragments as per `neo4j-graphql-js`, but can also be created using Nested Mutations. See xref::/type-definitions/types/interfaces.adoc[Interfaces]. +The following changes have been made to `@fulltext` arguments: -==== Union Types +* `queryName` has been added to specify a custom name for the top-level query that is generated. +* `name` has been renamed to `indexName` to avoid ambiguity with the new `queryName` argument. -Supported, queryable using inline fragments as per `neo4j-graphql-js`, but can also be created using Nested Mutations. See xref::/type-definitions/types/unions.adoc#type-definitions-unions[Unions]. - -=== Fields - -==== `_id` - -An `_id` field exposing the underlying node ID is not included in each type by default in `@neo4j/graphql` like it was in `neo4j-graphql-js`. If you require this functionality (however, it should be noted that underlying node IDs should not be relied on because they can be reused), you can include a field definition such as in the following type definition: +These changes mean that the following type definition is now invalid: [source, graphql, indent=0] ---- -type ExampleType { - _id: ID! @cypher(statement: "RETURN ID(this)") +type Movie @fulltext(indexes: [{ name: "MovieTitle", fields: ["title"] }]) { + title: String! } ---- -[[migration-guide-queries]] -== Queries - -Using `neo4j-graphql-js`, all of the arguments for a Query were root-level arguments. For example, for the following simple type definition: +The `name` argument now needs to be replaced with `indexName`: [source, graphql, indent=0] ---- -type Movie { - title: String! - averageRating: Float +type Movie @fulltext(indexes: [{ indexName: "MovieTitle", fields: ["title"] }]) { + title: String! } ---- -The following Query would have been generated: +As an example, the `queryName` argument can be used as: [source, graphql, indent=0] ---- -type Query { - Movie(title: String, averageRating: Float, first: Int, offset: Int, orderBy: [_MovieOrdering], filter: _MovieFilter): [Movie] +type Movie @fulltext(indexes: [{ queryName: "moviesByTitle", indexName: "MovieTitle", fields: ["title"] }]) { + title: String! } ---- -In `@neo4j/graphql`, the ethos has been to simplify the top-level arguments: +This means the top-level query is now `moviesByTitle` instead of `movieFulltextMovieTitle`: [source, graphql, indent=0] ---- type Query { - movies(where: MovieWhere, options: MovieOptions): [Movie]! + moviesByTitle(phrase: String!, where: MovieFulltextWhere, sort: [MovieFulltextSort!], limit: Int, offset: Int): [MovieFulltextResult!]! } ---- -Changes to note for migration: - -* Query fields were previously named in the singular, and in _PascalCase_ - they are now pluralized and in _camelCase_ -* Query return types were previously in nullable lists of nullable types - they are now non-nullable lists of non-nullable types, _e.g._ `[Movie]` is now `[Movie!]!`; ensuring either an array of defined `Movie` objects or an empty array. -* In this example, the `_MovieFilter` type has essentially been renamed to `MovieWhere`, the `filter` arguments renamed to `where`, and the top-level field arguments `title` and `averageRating` removed - see xref::migration/index.adoc#migration-guide-queries-filtering[Filtering (`where`)] below -* The `first`, `offset` and `orderBy` have been collapsed into the `MovieOptions` type and renamed `limit`, `offset` and `sort`, respectively - see xref::migration/index.adoc#migration-guide-queries-options[Sorting and Pagination (`options`)] below. +== Subscription options -[[migration-guide-queries-filtering]] -=== Filtering (`where`) +Subscriptions are no longer configured as a plugin, but as a feature within the `features` option. -Simple equality fields are no longer available at the root of Query fields. As a simple demonstration, a simple query using `neo4j-graphql-js` that looked like: +[cols="1,1"] +|=== +|Before | Now -[source, graphql, indent=0] +a| +[source, javascript] ---- -query { - Movie(title: "Forrest Gump") { - averageRating - } -} +const neoSchema = new Neo4jGraphQL({ + typeDefs, + plugins: { + subscriptions: plugin, + }, +}); ---- - -Is now changed to the following using `@neo4j/graphql`: - -[source, graphql, indent=0] +a| +[source, javascript] ---- -query { - movies(where: { title: "Forrest Gump" }) { - averageRating - } -} +const neoSchema = new Neo4jGraphQL({ + typeDefs, + features: { + subscriptions: plugin, + }, +}); ---- +|=== + +=== Default subscriptions -When discussing how the field `where` of type `MovieWhere` differs to the field `filter` of `_MovieFilter` the following table can be used for guidance: +The class `Neo4jGraphQLSubscriptionsSingleInstancePlugin` is no longer exported. +Instead, the default subscriptions behavior can be enabled by setting the `subscriptions` option to `true` . -.Comparing the fields of the `_MovieFilter` and `MovieWhere` types [cols="1,1"] |=== -|`neo4j-graphql-js` |`@neo4j/graphql` - -|`AND: [_MovieFilter!]` -|`AND: [MovieWhere!]` +|Before | Now -|`OR: [_MovieFilter!]` -|`OR: [MovieWhere!]` - -|`NOT: _MovieFilter!` -|`NOT: MovieWhere!` +a| +[source, javascript, indent=0] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, + plugin: { + subscriptions: new Neo4jGraphQLSubscriptionsSingleInstancePlugin(), + }, +}); +---- +a| +[source, javascript, indent=0] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, + features: { + subscriptions: true + }, +}); +---- +|=== -|`title: String` -|`title: String` +=== Neo4j GraphQL subscriptions AMQP package -|`title_in: [String!]` -|`title_IN: [String!]` +The name of the interface underlying the subscriptions system has changed from `Neo4jGraphQLSubscriptionsPlugin` to `Neo4jGraphQLSubscriptionsEngine`. +If you were previously using the `@neo4j/graphql-plugins-subscriptions-amqp` package, this has been changed to `@neo4j/graphql-amqp-subscriptions-engine` to reflect this underlying change. -|`title_contains: String` -|`title_CONTAINS: String` +To keep using it, uninstall the previous package and install the new one: -|`title_starts_with: String` -|`title_STARTS_WITH: String` +[source, bash, indent=0] +---- +npm uninstall @neo4j/graphql-plugins-subscriptions-amqp +npm install @neo4j/graphql-amqp-subscriptions-engine +---- -|`title_ends_with: String` -|`title_ENDS_WITH: String` +Then update any imports: -|`title_regexp: String` -|`title_MATCHES: String` +[cols="1,1"] +|=== +|From | To -|`averageRating: Float` -|`averageRating: Float` +a| +[source, javascript, indent=0] +---- +import { Neo4jGraphQLSubscriptionsAMQPPlugin } from "@neo4j/graphql-plugins-subscriptions-amqp"; +---- +a| +[source, javascript, indent=0] +---- +import { Neo4jGraphQLAMQPSubscriptionsEngine } from "@neo4j/graphql-amqp-subscriptions-engine"; +---- +|=== -|`averageRating_in: [Float!]` -|`averageRating_IN: [Float]` +And change the instantiations: -|`averageRating_lt: Float` -|`averageRating_LT: Float` +[cols="1,1"] +|=== +|From | To -|`averageRating_lte: Float` -|`averageRating_LTE: Float` +a| +[source, javascript, indent=0] +---- +const plugin = new Neo4jGraphQLSubscriptionsAMQPPlugin({ + connection: { + hostname: "localhost", + username: "guest", + password: "guest", + }, +}); +---- +a| +[source, javascript, indent=0] +---- +const subscriptionsEngine = new Neo4jGraphQLAMQPSubscriptionsEngine({ + connection: { + hostname: "localhost", + username: "guest", + password: "guest", + }, +}); +---- +|=== -|`averageRating_gt: Float` -|`averageRating_GT: Float` +=== Custom subscription plugins -|`averageRating_gte: Float` -|`averageRating_GTE: Float` -|=== +The underlying subscription system has not changed. +Custom behavior can be implemented the same way, by creating a class implementing the interface described in xref::subscriptions/engines.adoc#custom-subscription[Subscriptions engines]. -For filtering on relationship fields, the `_some`, `_none`, `_single` and `_every` filters are not yet implemented. +However, if using TypeScript, the exported interface to implement these classes has been renamed from `Neo4jGraphQLSubscriptionsPlugin` to `Neo4jGraphQLSubscriptionsEngine`. -[[migration-guide-queries-options]] -=== Sorting and Pagination (`options`) +== Updated directives -==== Sorting +A number of directives and their arguments have been renamed in order to make using `@neo4j/graphql` more intuitive. +Here is a table with all the changes: -Sorting has changed somewhat in `@neo4j/graphql`. For the example being used in this page, the `_MovieOrdering` type in `neo4j-graphql-js` was an enum which looked like the following: +[cols="1,2,2"] +|=== +|Before | Now | Example +|`@alias` +|Properties in the alias directive are now automatically escaped using backticks. +If you were using backticks in the `property` argument of your `@alias` directives, you should now remove the escape strings as this is covered by the library. +a| [source, graphql, indent=0] ---- -enum _MovieOrdering { - title_asc - title_desc - averageRating_asc - averageRating_desc +type User { + id: ID! @id + username: String! @alias(property: "dbUserName") } ---- -You could then query all movies ordered by title ascending by executing: +|`@callback` +|Renamed to `@populatedBy`. +Additionally, the `name` argument has been renamed to `callback` and it is still used to specify the callback used to populate the field's value. +a| +.Before +[source, graphql, indent=0] +---- +type User { + id: ID! @callback(name: "nanoid", operations: [CREATE]) + firstName: String! + surname: String! +} +---- +.Now [source, graphql, indent=0] ---- -query { - Movie(orderBy: [title_asc]) { - title +new Neo4jGraphQL({ + typeDefs, + features: { // changed from config + populatedBy: { // changed from callback + callbacks: { + nanoid: () => { return nanoid(); } + } } -} + } +}); ---- -In `@neo4j/graphql`, the sorting type `MovieSort` has become an input type with each field as an enum, like follows: +|`@computed` +a|Renamed to `@customResolver`. +Note that before and after these changes, a custom resolver needs to be defined as follows: + +[source, javascript, indent=0] +---- +new Neo4jGraphQL({ + typeDefs, + resolvers: { + User: { + fullName: ({ firstName, lastName }, args, context, info) => (`${firstName} ${lastName}`), + } + } +}); +---- +a| +.Before [source, graphql, indent=0] ---- -enum SortDirection { - ASC - DESC +type User { + firstName: String! + lastName: String! + fullName: String! @computed(from: ["firstName", "lastName"]) } +---- -input MovieSort { - title: SortDirection - averageRating: SortDirection +.Now +[source, graphql, indent=0] +---- +type User { + firstName: String! + lastName: String! + fullName: String! @customResolver(requires: ["firstName", "lastName"]) } ---- -To fetch all movies sorted by title ascending as per above, you would execute: - +|`from` +a| Renamed to `requires`. +In version 4.0.0, it is now possible to require non-scalar fields, which means it is also possible to require fields on related type. + +{nbsp} + +Additionally, the `requires` argument now accepts a GraphQL selection set instead of a list of strings and also validates the required selection set against your type definitions. +This means that if there is no field called `someFieldThatDoesNotExist`, an error would be thrown on startup if you tried to use the following type definitions: + +{nbsp} + [source, graphql, indent=0] ---- -query { - movies(options: { sort: [{ title: ASC }] }) { - title - } +type User { + firstName: String! + lastName: String! + fullName: String! @customResolver(requires: "firstName someFieldThatDoesNotExist") } ---- -==== Pagination - -Pagination is broadly similar, with the arguments just renamed and moved a level deeper. For example, to return "page 3, with 10 results per page" using `neo4j-graphql-js` was: - +a| +.Before [source, graphql, indent=0] ---- -query { - Movie(offset: 20, first: 10) { - title - } +type User { + firstName: String! + lastName: String! + fullName: String! @customResolver(requires: ["firstName", "lastName"]) } ---- -Using `@neo4j/graphql`, this will now be: - +.Now [source, graphql, indent=0] ---- -query { - movies(options: { offset: 20, limit: 10 }) { - title - } +type User { + firstName: String! + lastName: String! + fullName: String! @customResolver(requires: "firstName lastName") } ---- -[[migration-guide-mutations]] -== Mutations - -This section will walk through each operation available through GraphQL Mutations, and how to migrate each from `neo4j-graphql-js` to `@neo4j/graphql`. - -The examples in this section will be based off the following type definitions (which have been migrated over to `@neo4j/graphql` syntax): - +.Additional example [source, graphql, indent=0] ---- -type Actor { +interface Publication { + publicationYear: Int! +} + +type Author { name: String! - movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) + publications: [Publication!]! @relationship(type: "WROTE", direction: OUT) + publicationsWithAuthor: [String!]! + @customResolver( + requires: "name publications { publicationYear ...on Book { title } ... on Journal { subject } }" + ) } -type Movie { +type Book implements Publication { title: String! - averageRating: Float - actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) + publicationYear: Int! + author: [Author!]! @relationship(type: "WROTE", direction: IN) +} + +type Journal implements Publication { + subject: String! + publicationYear: Int! + author: [Author!]! @relationship(type: "WROTE", direction: IN) +} +---- + +|`@exclude` +|Replaced by `@query`, `@mutation`, and `@subscription`. +These new directives allow for fully granular configuration for each operation. +a|* `@exclude` -> `@query(read: false, aggregate: false) @mutation(operations: []) @subscription(events: [])`. + +* `@exclude(operations: [READ])` -> `@query(read: false, aggregate: false)`. + +* `@exclude(operation: [CREATE, UPDATE, DELETE])` -> `@mutation(operations: [])`. + +|`@id` +a|Deprecated with _all_ of its arguments removed and/or replaced. +a|*`autogenerate` -> `@unique`* + +The default value was `true`. +If set to `false`, the `@id` directive was almost a no-op only used to manage a unique node property constraint. +Use the `@unique` directive instead. + +{nbsp} + +*`global` -> `@relayId`* + +This argument was used to configure the field that would form the global node identifier for Relay. +This functionality has been moved into its own directive, `@relayId`. The use of `@relayId` will ensure a unique node property constraint for the field. + +{nbsp} + +*`@id` -> `unique` + `@id`* + +The `@id` directive used to also manage unique node property constraints for a field. +This functionality has been removed. +Use the `@unique` directive in combination with `@id` if you want the field to be backed by a constraint. + +|`@plural` +|Removed from `@node` and replaced by the `@plural` directive. +It takes the pluralized type name using the `value` argument. +a| +.Invalid `plural` type definition +[source, graphql, indent=0] +---- +type Tech @node(label: "TechDB", plural: "Techs") { + name: String } ---- -A summary of migration points is as follows: - -* Mutations which were previously in the singular and in _PascalCase_ are now pluralized and in _camelCase_ - for example `CreateMovie` has become `createMovies` -* Connect and disconnect Mutations are no longer present, these operations are accessed through an update Mutation -* The object(s) being mutated are returned as a nested field, to allow for metadata about the operation to be added in future -* Mutation arguments are now commonly named between different types, but with different input types - such as `where` and `input` - -> Note that xref::mutations/index.adoc[Mutations] in `@neo4j/graphql` are incredibly powerful, and it is well worthwhile reading about them in full. You might find that you can collapse multiple current mutations down into one! - -=== Creating - -For creating nodes, the input arguments of `neo4j-graphql` have been moved inside an `input` argument in `@neo4j/graphql`. - -For example, creating a movie using the old library: +.Updated version [source, graphql, indent=0] ---- -mutation { - CreateMovie(title: "Forrest Gump") { - title - } +type Tech @node(label: "TechDB") @plural(value: "Techs") { + name: String } ---- -Looks like the following using `@neo4j/graphql`: - +|`label` and `additionalLabels` +a|Removed from `@node` and replaced by `labels`. +It accepts a list of string labels that are used when a node of the given GraphQL type is created. + +{nbsp} + +Note that defining `labels` means taking control of the database labels of the node. +Indexes and constraints in Neo4j only support a single label, for which the first element of the `labels` argument will be used. + +{nbsp} + +As before, providing none of these arguments results in the node label being the same as the GraphQL type name. +This can cause implications on constraits. +For instance, in the case where unique constraint is asserted for the label `Tech` and the property `name`: + +{nbsp} + [source, graphql, indent=0] ---- -mutation { - createMovies(input: { title: "Forrest Gump" }) { - movies { - title - } - } +type Tech @node(labels: ["Tech", "TechDB"]) { + name: String @unique } ---- - -Note that `movies` must also be first selected in the selection set. - -=== Updating - -An update Mutation using `neo4j-graphql-js` had all of the arguments at the root of the Mutation, including the filter and fields to change. - -This has all changed in `@neo4j/graphql`, with a `where` argument to select the node, and then an `update` argument (amongst many others) to select what to update. - -For example, updating the average rating of the Movie Forrest Gump: - +a| +.Current equivalent to `label` [source, graphql, indent=0] ---- -mutation { - UpdateMovie(title: "Forrest Gump", averageRating: 5.0) { - title - averageRating - } +type Tech @node(label: "TechDB") { + name: String +} +# becomes +type Tech @node(labels: ["TechDB"]) { + name: String } ---- -Will look like the following using the new library: - +.Current equivalent to `additionalLabels` [source, graphql, indent=0] ---- -mutation { - updateMovies(where: { title: "Forrest Gump" }, update: { averageRating: 5.0 }) { - movies { - title - averageRating - } - } +type Tech @node(additionalLabels: ["TechDB"]) { + name: String +} +# becomes +type Tech @node(labels: ["Tech", "TechDB"]) { + name: String } ---- -=== Deleting - -The arguments for selecting which node to delete have now been moved into a `where` argument. - -Additionally, the return value is now a `DeleteInfo` object informing how many nodes and relationships were deleted. +.Current equivalent to both arguments +[source, graphql, indent=0] +---- +type Tech @node(label: "TechDB", additionalLabels: ["AwesomeTech"]) { + name: String +} +# becomes +type Tech @node(labels: ["TechDB", "AwesomeTech"]) { + name: String +} +---- -For example, deleting a movie: +|`@queryOptions` and `limit` +| Removed and moved to `@limit`. +a| +.Outdated example +[source, graphql, indent=0] +---- +type Record @queryOptions(limit: { default: 10, max: 100 }) { + id: ID! +} +---- +.Updated version using `@limit` [source, graphql, indent=0] ---- -mutation { - DeleteMovie(title: "Forrest Gump") { - title - } +type Record @limit(default: 10, max: 100) { + id: ID! } ---- -Looks like the following using `@neo4j/graphql`: +|`@readonly` and `@writeonly` +|Removed and replaced by the `@selectable` and `@settable` directives. +They can be used to configure not only if fields are readable or writable, but also when they should be readable or writable. +a|* `@readonly` -> `@settable(onCreate: false, onUpdate: false)` +* `@writeonly` -> `@selectable(onRead: false, onAggregate: false)` +|`@query` and `@relationship` +|Aggregation operations are no longer generated by default. +They can be enabled case by case using the directives xref::/schema-configuration/type-configuration.adoc#_query[`@query`] and xref::/schema-configuration/field-configuration.adoc#_relationship[`@relationship`]. +a| [source, graphql, indent=0] ---- -mutation { - deleteMovies(where: { title: "Forrest Gump" }) { - nodesDeleted - relationshipsDeleted - } +type Movie { + title: String! +} + +type Actor @query(aggregate: true) { + name: String! + actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, aggregate: true) } ---- +|=== + +[relationship-aggregate] +== Relationship updates + +Here are the changes and updates to `@relationship`-related features. -=== Connecting +[discrete] +=== *Relationship types are now automatically escaped* -Using `neo4j-graphql-js`, connecting two of the nodes in this example would have been achieved by executing either the `AddMovieActors` or `AddActorMovies` Mutation. +Relationship types are now automatically escaped. +If you have previously escaped your relationship types using backticks, you must now remove these as this is covered by the library. -In `@neo4j/graphql`, this is achieved by specifying the `connect` argument on either the `updateMovies` or `updateActors` Mutation. +[discrete] +=== *`@relationshipProperties` now mandatory* -For example: +Current changes require the distinction between interfaces that are used to specify relationship properties, and others. +Therefore, the `@relationshipProperties` directive is now required on all relationship property interfaces. +If it is not included, an error is thrown. + +As a result, in version 4.0.0, the following type definitions are invalid: [source, graphql, indent=0] ---- -mutation { - AddMovieActors(from: { name: "Tom Hanks" }, to: { title: "Forrest Gump" }) { - from { - name - } - to { - title - } - } +type Person { + name: String! + movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedIn") +} + +type Movie { + title: String! + actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") +} + +interface ActedIn { + screenTime: Int! } ---- -Would become the following using `@neo4j/graphql`: +`ActedIn` must be decorated with `@relationshipProperties`: [source, graphql, indent=0] ---- -mutation { - updateMovies( - where: { title: "Forrest Gump" } - connect: { actors: { where: { node: { name: "Tom Hanks" } } } } - ) { - movies { - title - actors { - name - } - } - } +interface ActedIn @relationshipProperties { + screenTime: Int! } ---- -Note, there are many ways to achieve the same goal using the powerful Mutation ability of `@neo4j/graphql`, so do what feels best for your data! +[discrete] +=== Duplicate relationship fields are now checked for -=== Disconnecting +In 3.0.0, it was possible to define schemas with types that have multiple relationship fields connected by the same type of relationships. +Now, this kind of scenario is detected during schema generation and an error is thrown so developers are informed to fix the type definitions. -Similarly to connecting, using `neo4j-graphql-js`, disconnecting two of the nodes in this example would have been achieved by executing either the `RemoveMovieActors` or `RemoveActorMovies` Mutation. - -In `@neo4j/graphql`, this is achieved by specifying the `disconnect` argument on either the `updateMovies` or `updateActors` Mutation. - -For example: +Here is an example of what is now considered invalid with these checks: [source, graphql, indent=0] ---- -mutation { - RemoveMovieActors(from: { name: "Tom Hanks" }, to: { title: "Forrest Gump" }) { - from { - name - } - to { - title - } - } +type Team { + player1: Person! @relationship(type: "PLAYS_IN", direction: IN) + player2: Person! @relationship(type: "PLAYS_IN", direction: IN) + backupPlayers: [Person!]! @relationship(type: "PLAYS_IN", direction: IN) +} + +type Person { + teams: [Team!]! @relationship(type: "PLAYS_IN", direction: OUT) } ---- -Would become the following using `@neo4j/graphql`: +In this example, there are multiple fields in the `Team` type which have the same `Person` type, the same `@relationship` type and ("PLAYS_IN") direction (IN). This is an issue when returning data from the database, as there would be no difference between `player1`, `player2` and `backupPlayers`. Selecting these fields would then return the same data. -[source, graphql, indent=0] +These checks can be disabled by disabling all validation in the library, however, this is not recommended unless in production with 100% confidence of type definitions input. + +[source, javascript, indent=0] ---- -mutation { - updateMovies( - where: { title: "Forrest Gump" } - disconnect: { actors: { where: { node: { name: "Tom Hanks" } } } } - ) { - movies { - title - actors { - name - } - } - } -} +const neoSchema = new Neo4jGraphQL({ + typeDefs, + validate: false, +}); ---- -In the result field `actors`, Tom Hanks should no longer be present. +== `cypherParams` +In 3.0.0, `cypherParams` was available in the context to provide the ability to pass arbitrary parameters to a custom Cypher query. +This functionality remains in 4.0.0, but you no longer have to use the `$cypherParams` prefix to reference these parameters. diff --git a/modules/ROOT/pages/migration/v4-migration/ogm.adoc b/modules/ROOT/pages/migration/ogm.adoc similarity index 70% rename from modules/ROOT/pages/migration/v4-migration/ogm.adoc rename to modules/ROOT/pages/migration/ogm.adoc index 216cd84a..28e2c9aa 100644 --- a/modules/ROOT/pages/migration/v4-migration/ogm.adoc +++ b/modules/ROOT/pages/migration/ogm.adoc @@ -1,11 +1,12 @@ = OGM -:page-aliases: guides/v4-migration/ogm.adoc +:description: This page describes what updates were made to the OGM tool in version 4.0.0 of the Neo4j GraphQL Library. +:page-aliases: guides/v4-migration/ogm.adoc, migration/v4-migration/ogm.adoc +This page describes what updates were made to the OGM tool in version 4.0.0 of the Neo4j GraphQL Library. -== Specifying which database to use when using the OGM +== Database specification The method to specify the database that the OGM should use has been changed. - This was previously configured using the `driverConfig` option: [source, javascript, indent=0] @@ -27,7 +28,7 @@ const driver = neo4j.driver( const ogm = new OGM({ typeDefs, driver, driverConfig: { database: "some-other-database" } }); ---- -This has now been raised to the top-level: +And now it has been raised to the top-level: [source, javascript, indent=0] ---- diff --git a/modules/ROOT/pages/migration/v2-migration.adoc b/modules/ROOT/pages/migration/v2-migration.adoc deleted file mode 100644 index a6d93ec6..00000000 --- a/modules/ROOT/pages/migration/v2-migration.adoc +++ /dev/null @@ -1,606 +0,0 @@ -[[v2-migration]] -= 2.0.0 Migration -:page-aliases: guides/v2-migration/index.adoc, guides/v2-migration/miscellaneous.adoc, guides/v2-migration/unions.adoc, guides/v2-migration/mutations.adoc - - -Version 2.0.0 of `@neo4j/graphql` adds support for relationship properties, with some breaking changes to facilitate these new features. All of the required changes will be on the client side, and this guide will walk through what has changed. - -== How to Upgrade - -Simply update `@neo4j/graphql` using npm or your package manager of choice: - -[source, bash, indent=0] ----- -npm update @neo4j/graphql ----- - -From this point on, it is primarily Mutations which will form the bulk of the migration. - -[[v2-migration-mutations]] -== Mutations - -The most broadly affected area of functionality by the 2.0.0 upgrade are the nested operations of Mutations, to facilitate the mutation of and filtering on relationship properties. - -The examples in this section will be based off the following type definitions: - -[source, graphql, indent=0] ----- -type Actor { - name: String! - movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) -} - -type Movie { - title: String! - actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) -} ----- - -The theme that you will notice during this section is that as a general rule of thumb, a `node` field will need adding to your inputs where it will also be possible to filter on relationship properties. - -[[v2-migration-mutations-create]] -=== Create - -Focussing on the `createMovies` mutation, notice that the definition of the `createMovies` mutation is unchanged: - -[source, graphql, indent=0] ----- -input MovieCreateInput { - title: String! - actors: MovieActorsFieldInput -} - -type Mutation { - createMovies(input: [MovieCreateInput!]!): CreateMoviesMutationResponse! -} ----- - -There are no changes to any of the arguments or types at this level. However, within its nested operations, type modifications have taken place to allow for relationship properties. - -In practice, take a mutation that creates the film "The Dark Knight" and then: - -* Creates a new actor "Heath Ledger" -* Connects to the existing actor "Christian Bale" - -In the previous version of the library, this would have looked like this: - -[source, graphql, indent=0] ----- -mutation { - createMovies( - input: [ - { - title: "The Dark Knight" - actors: { - create: [ - { - name: "Heath Ledger" - } - ] - connect: [ - { - where: { - name: "Christian Bale" - } - } - ] - } - } - ] - ) { - movies { - title - } - } -} ----- - -This will now have to look like this in order to function in the same way: - -[source, graphql, indent=0] ----- -mutation { - createMovies( - input: [ - { - title: "The Dark Knight" - actors: { - create: [ - { - node: { - name: "Heath Ledger" - } - } - ] - connect: [ - { - where: { - node: { - name: "Christian Bale" - } - } - } - ] - } - } - ] - ) { - movies { - title - } - } -} ----- - -Note the additional level "node" before specifying the actor name for the create operation and in the connect where. This additional level allows for the setting of relationship properties for the new relationship, and filtering on existing relationship properties when looking for the node to connect to. See the page xref::mutations/index.adoc[mutations] for details on this. - -=== Update - -Focussing on the `updateMovies` mutation, notice that the definition of the `updateMovies` mutation is unchanged: - -[source, graphql, indent=0] ----- -type Mutation { - updateMovies( - where: MovieWhere - update: MovieUpdateInput - connect: MovieConnectInput - disconnect: MovieDisconnectInput - create: MovieRelationInput - delete: MovieDeleteInput - ): UpdateMoviesMutationResponse! -} ----- - -The `create` and `connect` nested operations are primarily the same as in the `createMovies` mutation, so please see the <> section for the difference for these operations. - -The `delete` nested operation is primarily the same as in the `deleteMovies` mutation, so please see the <> section for that. - -==== Update - -For example, say that you accidentally misspelt Christian Bale's surname and wanted to fix that. In the previous version, you might have achieved that by: - -[source, graphql, indent=0] ----- -mutation { - updateMovies( - where: { - title: "The Dark Knight" - } - update: { - actors: [ - { - where: { - name_ENDS_WITH: "Bail" - } - update: { - name: "Christian Bale" - } - } - ] - } - ) { - movies { - title - actors { - name - } - } - } -} ----- - -This will now have to look like this in order to function in the same way: - -[source, graphql, indent=0] ----- -mutation { - updateMovies( - where: { - title: "The Dark Knight" - } - update: { - actors: [ - { - where: { - node: { - name_ENDS_WITH: "Bail" - } - } - update: { - node: { - name: "Christian Bale" - } - } - } - ] - } - ) { - movies { - title - actors { - name - } - } - } -} ----- - -Note the added layer of abstraction of `node` in both the `where` and `update` clauses. - -==== Disconnect - -For example, say you mistakenly put Ben Affleck as playing the role of Batman in "The Dark Knight", and you wanted to disconnect those nodes. In the previous version, this would have looked like: - -[source, graphql, indent=0] ----- -mutation { - updateMovies( - where: { - title: "The Dark Knight" - } - disconnect: { - actors: [ - { - where: { - name: "Ben Affleck" - } - } - ] - } - ) { - movies { - title - actors { - name - } - } - } -} ----- - -This will now have to look like this in order to function in the same way: - -[source, graphql, indent=0] ----- -mutation { - updateMovies( - where: { - title: "The Dark Knight" - } - disconnect: { - actors: [ - { - where: { - node: { - name: "Ben Affleck" - } - } - } - ] - } - ) { - movies { - title - actors { - name - } - } - } -} ----- - -[[v2-migration-mutations-delete]] -=== Delete - -Focussing on the `deleteMovies` mutation, notice that the definition of the `deleteMovies` mutation is unchanged: - -[source, graphql, indent=0] ----- -input MovieDeleteInput { - actors: [MovieActorsDeleteFieldInput!] -} - -type Mutation { - deleteMovies(where: MovieWhere, delete: MovieDeleteInput): DeleteInfo! -} ----- - -There are no changes to any of the arguments or types at this level, but there are some details to note in the `MovieActorsDeleteFieldInput` type. - -Previously, you would have expected this to look like: - -[source, graphql, indent=0] ----- -input MovieActorsDeleteFieldInput { - delete: ActorDeleteInput - where: ActorWhere -} ----- - -This allowed you to filter on fields of the `Actor` type and delete based on that. However, following this upgrade, you will find: - -[source, graphql, indent=0] ----- -input MovieActorsDeleteFieldInput { - delete: ActorDeleteInput - where: MovieActorsConnectionWhere -} ----- - -This means that not only can you filter on node properties, but also relationship properties, in order to find and delete `Actor` nodes. - -In practice, a mutation that deletes the film "The Dark Knight" and the related actor "Christian Bale" would have previously looked like this: - -[source, graphql, indent=0] ----- -mutation { - deleteMovies( - where: { - title: "The Dark Knight" - } - delete: { - actors: { - where: { - name: "Christian Bale" - } - } - } - ) { - nodesDeleted - relationshipsDeleted - } -} ----- - -This will now have to look like this in order to function in the same way: - -[source, graphql, indent=0] ----- -mutation { - deleteMovies( - where: { - title: "The Dark Knight" - } - delete: { - actors: { - where: { - node: { - name: "Christian Bale" - } - } - } - } - ) { - nodesDeleted - relationshipsDeleted - } -} ----- - -Note the additional level "node" before specifying the actor name. - -[[v2-migration-unions]] -== Unions - -In this release, the decision was made to take the opportunity to overhaul the existing support for unions on relationship fields, laying down the foundations for adding top-level union support in the future. - -All examples in this section will be based off the following type definitions: - -[source, graphql, indent=0] ----- -type Actor { - name: String! - actedIn: [Production!]! @relationship(type: "ACTED_IN", direction: OUT) -} - -type Movie { - title: String! - actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) -} - -type Series { - title: String! - actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) -} - -union Production = Movie | Series ----- - -=== Input types - -The structure of input types for union queries and mutations have been changed for user friendliness, and a more consistent API. - -Essentially, field names which were previously of template `_` (for example, "actedIn_Movie") are now an object, with the field name at the top, and the member types under it. - -For example, a Mutation which would have previously been: - -[source, graphql, indent=0] ----- -mutation { - createActors( - input: [ - { - name: "Tom Hiddleston" - actedIn_Movie: { - create: [ - { - title: "The Avengers" - } - ] - } - actedIn_Series: { - create: [ - { - title: "Loki" - } - ] - } - } - ] - ) -} ----- - -Will now be: - -[source, graphql, indent=0] ----- -mutation { - createActors( - input: [ - { - name: "Tom Hiddleston" - actedIn: { - Movie: { - create: [ - { - node: { - title: "The Avengers" - } - } - ] - } - Series: { - create: [ - { - node: { - title: "Loki" - } - } - ] - } - } - } - ] - ) -} ----- - -Note the change in structure for union input, but also the additional `node` level which enables the use of relationship properties. These changes are consistent across all operations, including `where`. - -=== Filtering union fields - -There has been a slight change to how you filter union fields, adding a `where` level above each union member. For example, for a query which would have used to have looked like: - -[source, graphql, indent=0] ----- -query { - actors { - name - actedIn(Movie: { "The Avengers" }) { - ... on Movie { - title - } - } - } -} ----- - -This will now be written like: - -[source, graphql, indent=0] ----- -query { - actors { - name - actedIn(where: { Movie: { "The Avengers" }}) { - ... on Movie { - title - } - } - } -} ----- - -Furthermore, the where argument used now dictates which union members are returned from the database, to prevent overfetching. Please see xref::troubleshooting.adoc#appendix-preventing-overfetching[this page] for background and explanation of this decision. - -[[v2-migration-miscellaneous]] -== Miscellaneous - -=== `skip` renamed to `offset` - -In the release of Apollo Client 3.0, it became a bit more opinionated about pagination, favouring `offset` and `limit` over `skip` and `limit`. Acknowledging that the majority of users will be using Apollo Client 3.0, the page-based pagination arguments have been updated to align with this change. - -For example, fetching page 3 of pages of 10 movies would have looked like the following in version `1.x`: - -[source, graphql, indent=0] ----- -query { - movies(options: { skip: 20, limit: 10 }) { - title - } -} ----- - -This will now need to queried as follows: - -[source, graphql, indent=0] ----- -query { - movies(options: { offset: 20, limit: 10 }) { - title - } -} ----- - -=== Count queries - -Whilst not a necessary migration step, if you are using page-based pagination, it's important to note the addition of count queries in version 2.0.0. These will allow you to calculate the total number of pages for a particular filter, allowing you to implement much more effective pagination. - -== Schema validation - -In version 2.0.0, there are greater levels of schema validation. However, upon upgrading, you might find that validation is too strict (for example if using certain generated types in your definitions). You can temporarily disable this new validation on construction: - -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - config: { - skipValidateTypeDefs: true, - }, -}) ----- - -If you need to do this, please report the scenario as an issue on GitHub. - -=== `_IN` and `_NOT_IN` filters on relationships removed - -There were previously `_IN` and `_NOT_IN` filters for one-to-many and one-to-one relationships, but these were surplus to requirements, and didn't match for all cardinalities (many-to-many relationships don't have `_INCLUDES` and `_NOT_INCLUDES`). These may be added back in the future if and when we look more holistically at distinguishing between different relationship cardinalities. - -You can still achieve identical filters through different routes. For example, if you had the following schema: - -[source, graphql, indent=0] ----- -type Movie { - title: String! - director: Director! @relationship(type: "DIRECTED", direction: IN) -} - -type Director { - name: String! - movies: [Movie!]! @relationship(type: "DIRECTED", direction: OUT) -} ----- - -You would have been able to run the following query: - -[source, graphql, indent=0] ----- -query { - movies(where: { director_IN: [{ name: "A" }, { name: "B" }] }) { - title - } -} ----- - -You can still achieve exactly the same filter with the following: - -[source, graphql, indent=0] ----- -query { - movies(where: { director: { OR: [{ name: "A" }, { name: "B" }]} }) { - title - } -} ----- diff --git a/modules/ROOT/pages/migration/v3-migration.adoc b/modules/ROOT/pages/migration/v3-migration.adoc deleted file mode 100644 index d9657ba9..00000000 --- a/modules/ROOT/pages/migration/v3-migration.adoc +++ /dev/null @@ -1,253 +0,0 @@ -[[v3-migration]] -= 3.0.0 Migration -:page-aliases: guides/v3-migration/index.adoc - - -This document lists all breaking changes from version 2.x.y to 3.0.0 and how to update. - -== How to upgrade -Simply update `@neo4j/graphql` using npm or your package manager of choice: - -[source, bash, indent=0] ----- -npm update @neo4j/graphql ----- - -== Asynchronous schema generation -Schema generation is now asynchronous. Instead of using the property `schema`, now the method `getSchema` will return the schema -as a `Promise`. This means that creating a server now requires awaiting for that method: - -Instead of -[source, JavaScript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); -const server = new ApolloServer({ - schema: neoSchema.schema, -}); ----- - -Now you'll need to do the following: - -[source, JavaScript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); -neoSchema.getSchema().then((schema) => { - const server = new ApolloServer({ - schema: schema - }); -}); ----- - -== Relationship changes -This release contains an overhaul of our relationship validations, which will require a few changes to the schema. - -=== Many-to-* relationships -To improve consistency and validation, **all** "many-to-*" relationships need to be defined as _required_ in the schema: - -[source, graphql, indent=0] ----- -type Movie { - actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) - director: Director @relationship(type: "DIRECTED", direction: IN) -} ----- - -Note that any other notation, such as `[Actor]` or `[Actor!]` will **not** be valid. "One-to-one" relationships -such as `Director` remain unchanged. - -=== Relationship cardinality -Runtime checks for "one-to*" relationships have been added in this release, ensuring that the correct number of relationships exist. This means that some -databases with inconsistent relationships between the schema definition and the actual data may now fail in some queries. -This may have happened due to different reasons such as direct changes in the database or changes to the type definitions. -Previous versions of `@neo4j/graphql` did not have any consistency check, so normal use of these versions may have lead to -inconsistent relationships. - -For these cases, please ensure that the database is following your schema definition or update the schema to reflect the -actual existing relationships, taking care of which relationships are 1-to-* or many-to-many. - -== Count query no longer supported -Queries using `count` at the root level are no longer supported. For example: -[source, graphql, indent=0] ----- -query { - usersCount -} ----- - -The same operation, can now be achieved with a xref::queries-aggregations/queries.adoc#_counting_using_aggregation[count aggregation] query: - -[source, graphql, indent=0] ----- -query { - usersAggregate { - count - } -} ----- - -=== Relationship filters -`where` filters for relationship queries now explicitly state `ALL`, `NONE`, `SINGLE`, and `SOME` as part of filter name. - -Queries using old relationship filters, will now need to use `\{relationship\}_SOME`. For example: - -[source, graphql, indent=0] ----- -query { - movies(where: { - actors: { - name: "John" - } - }) { - title - } -} ----- - -Should be: - -[source, graphql, indent=0] ----- -query { - movies(where: { - actors_SOME: { - name: "John" - } - }) { - title - } -} ----- - -And, instead of `_NOT`, `_NONE` should be used. - -NOTE: Old queries will still work in this release, but are marked as `@deprecated` and will not be available in the future. - -== `@ignore` directive renamed to `@computed` -To better reflect its intended usage, the `@ignore` directive is now named `@computed`. Behaviour is unchanged, so you just need to -rename this directive in your schema. - -== Auth plugin system -label:deprecated[] - -Auth setup now relies on _plugins_ to setup the configuration. -You'll need to install `@neo4j/graphql-plugin-auth` or a custom plugin. - -=== JWT auth -For JWT authorization, instead of the previous configuration: -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - config: { - jwt: { - secret - } - } -}); ----- - -Now the configuration should be passed through `Neo4jGraphQLAuthJWTPlugin`: - -[source, javascript, indent=0] ----- -import { Neo4jGraphQL } from "@neo4j/graphql"; -import { Neo4jGraphQLAuthJWTPlugin } from "@neo4j/graphql-plugin-auth"; - -const neoSchema = new Neo4jGraphQL({ - typeDefs, - plugins: { - auth: new Neo4jGraphQLAuthJWTPlugin({ - secret: "super-secret" - }) - } -}); ----- - - -=== JWKS decoding - -https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets[JSON Web Key Sets] are now supported through `Neo4jGraphQLAuthJWKSPlugin`. - -Instead of setting the endpoint directly: -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - config: { - jwt: { - jwksEndpoint: "https://YOUR_DOMAIN/.well-known/jwks.json" - } - } -}); ----- - -Now the `Neo4jGraphQLAuthJWKSPlugin` would take care of that: -[source, javascript, indent=0] ----- -import { Neo4jGraphQL } from "@neo4j/graphql"; -import { Neo4jGraphQLAuthJWKSPlugin } from "@neo4j/graphql-plugin-auth"; - -const neoSchema = new Neo4jGraphQL({ - typeDefs, - plugins: { - auth: new Neo4jGraphQLAuthJWKSPlugin({ - jwksEndpoint: "https://YOUR_DOMAIN/well-known/jwks.json", - }) - } -}); ----- - -NOTE: Please, refer to xref::authentication-and-authorization/index.adoc[auth setup] before setting up auth. - -== Types plurals changes -To improve consistency, some automatically generated plurals (e.g. `createActors`) have changed. This may cause issues if -your types use conventions such as `snake_case`. - -Because of this, you may find generated queries and mutations may have different names. If you encounter this problem, -please update your clients to use the new query names or use the `plural` option in the xref::/type-definitions/directives/database-mapping.adoc#type-definitions-node[@node directive] -to force a custom plural value. - -== Custom Directives -Defining and applying custom directives has changed significantly, if you are using or plan to use custom directives, make -sure to check the up-to-date documentation on xref::/type-definitions/directives/custom-directives.adoc[custom directives]. - -== Types changes -Some automatically generated types have changed to improve consistency. -These should not require any changes from most developers, unless types names are directly used. - -Some automatically generated types have changed to improve consistency. -These should not require any changes from the developer in most cases, unless in cases where types names are directly used. - -=== Removal of nested operation fields for `connectOrCreate` -Input types for `onCreate` in `connectOrCreate` operations no longer accept relationship fields. They were originally added in error and did not function as one would expect, so there is no regression in functionality. - -=== Non Nullable Aggregation Results -Aggregation results may now be non-nullable for required fields, yielding more accurate types. - -For example, for the following types: -[source, graphql, indent=0] ----- -type User { - name: String! - lastName: String -} ----- - -Will yield different types for aggregations over `name` and `lastName`: -[source, graphql, indent=0] ----- -type UserAggregateSelection { - count: Int! - name: StringAggregateSelectionNonNullable! - lastName: StringAggregateSelectionNullable! -} ----- - -=== ConnectionWhere types renamed -`ConnectionWhere` types renamed to improve consistency with other similarly named types. - -== Neo4j support -Neo4j 4.1 is no longer supported in 3.0.0, inline with the https://neo4j.com/developer/kb/neo4j-supported-versions/[supported versions list]. - -== GraphQL support -`graphql@^15.0.0` is no longer supported, please upgrade to `graphql@^16.0.0` using `npm` or the package manager of your choice. diff --git a/modules/ROOT/pages/migration/v4-migration/index.adoc b/modules/ROOT/pages/migration/v4-migration/index.adoc deleted file mode 100644 index 6375ab27..00000000 --- a/modules/ROOT/pages/migration/v4-migration/index.adoc +++ /dev/null @@ -1,1015 +0,0 @@ -[[v4-migration]] -= 4.0.0 Migration -:page-aliases: guides/v4-migration/index.adoc - - -This document lists all breaking changes from version 3.x.y to 4.0.0 and how to update. - -== How to upgrade -Simply update `@neo4j/graphql` using npm or your package manager of choice: - -[source, bash, indent=0] ----- -npm update @neo4j/graphql ----- - -== Constructor arguments - -If you were passing any arguments from https://the-guild.dev/graphql/tools/docs/api/interfaces/schema_src.iexecutableschemadefinition[`IExecutableSchemaDefinition`] into the library other than `typeDefs` and `resolvers`, these are no longer supported. - -=== Removal of `config` - -==== `debug` - -The programmatic toggle for debug logging has been moved from `config.enableDebug` to simply `debug`. - -An example of `enableDebug`: - -[source, javascript, indent=0] ----- -const { Neo4jGraphQL } = require("@neo4j/graphql"); -const neo4j = require("neo4j-driver"); -const { ApolloServer } = require("apollo-server"); - -const typeDefs = ` - type Movie { - title: String! - } -`; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("neo4j", "password") -); - -const neoSchema = new Neo4jGraphQL({ - typeDefs, - driver, - config: { - enableDebug: true, - } -}); ----- - -This now becomes: - -[source, javascript, indent=0] ----- -const { Neo4jGraphQL } = require("@neo4j/graphql"); -const neo4j = require("neo4j-driver"); -const { ApolloServer } = require("apollo-server"); - -const typeDefs = ` - type Movie { - title: String! - } -`; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("neo4j", "password") -); - -const neoSchema = new Neo4jGraphQL({ - typeDefs, - driver, - debug: true, -}); ----- - -==== `driverConfig` moved to context - -Session configuration is now available only in the context under the `sessionConfig` key. - -This was previously `driverConfig`, available in both the constructor and in the context: - -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - config: { - driverConfig: { - database: "different-db" - }, - }, -}) ----- - -The new `sessionConfig` key is only available in the context: - -[source, javascript, indent=0] ----- -import { ApolloServer } from '@apollo/server'; -import { startStandaloneServer } from '@apollo/server/standalone'; -import { Neo4jGraphQL } from "@neo4j/graphql"; -import neo4j from "neo4j-driver"; - -const typeDefs = `#graphql - type User { - name: String - } -`; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("neo4j", "password") -); - -const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); - -const server = new ApolloServer({ - schema: await neoSchema.getSchema(), -}); - -await startStandaloneServer(server, { - context: async ({ req }) => ({ sessionConfig: { database: "my-database" }}), -}); ----- - -The `bookmarks` key has been removed because it is no longer needed with the bookmark manager of the newer driver. - -==== `enableRegex` replaced by `MATCHES` in features.filters - -`config.enableRegex` has been replaced by `MATCHES` in features.filters. With this change comes more granularity in the feature configuration. You can now enable the `MATCHES` filter on `String` and `ID` fields separately. - -A direct replacement of the `enableRegex: true` configuration would be as follows: - -[source, javascript, indent=0] ----- -neoSchema = new Neo4jGraphQL({ - typeDefs, - features: { - filters: { - String: { - MATCHES: true, - }, - ID: { - MATCHES: true, - }, - }, - }, -}); ----- - -==== `queryOptions` moved to the context - -If you had a need to pass in Cypher query options for query tuning, this interface has been changed. - -The config option `queryOptions` has now become `cypherQueryOptions` inside the context function, and it now accepts simple strings instead of enums. - -The following is an example before the change: - -[source, javascript, indent=0] ----- -const { Neo4jGraphQL, CypherRuntime } = require("@neo4j/graphql"); -const { ApolloServer } = require("apollo-server"); - -const typeDefs = ` - type Movie { - title: String! - } -`; - -const neoSchema = new Neo4jGraphQL({ - typeDefs, - config: { - queryOptions: { - runtime: CypherRuntime.INTERPRETED, - }, - }, -}); ----- - -This is what is required after the change: - -[source, javascript, indent=0] ----- -const { Neo4jGraphQL } = require("@neo4j/graphql"); -const { ApolloServer } = require("apollo-server"); - -const typeDefs = ` - type Movie { - title: String! - } -`; - -const neoSchema = new Neo4jGraphQL({ - typeDefs, -}); - -const server = new ApolloServer({ - schema: await neoSchema.getSchema(), -}); - -await startStandaloneServer(server, { - context: async ({ req }) => ({ cypherQueryOptions: { runtime: "interpreted" }}), -}); ----- - -This reflects the fact that the Cypher query options are set on a per-request basis. - -[[startup-validation]] -==== `skipValidateTypeDefs` - -The argument `skipValidateTypeDefs` has been moved to the top-level of the constructor input and renamed `validate`, which defaults to `true`. - -To disable type definition validation, the following config option should be used: - -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - validate: false, -}) ----- - -If you started using the `config.startupValidation` option, this has also been rolled into the same `validate` setting for simplicity. -The `resolvers` option of this is now just a warning, and `noDuplicateRelationshipFields` is now a mandatory check rolled into `validate`. - -[subscriptions-options] -=== Subscription options - -Subscriptions are no longer configured as a plugin, but as a feature within the `features` option. - -This means that, instead of: - -[source, javascript] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - plugins: { - subscriptions: plugin, - }, -}); ----- - -Subscriptions are now defined as: - -[source, javascript] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - features: { - subscriptions: plugin, - }, -}); ----- - -==== Default subscriptions - -The class `Neo4jGraphQLSubscriptionsSingleInstancePlugin` is no longer exported. -Instead, the default subscriptions behavior can be enabled by setting the `subscriptions` option to `true` . - -Instead of: - -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - plugin: { - subscriptions: new Neo4jGraphQLSubscriptionsSingleInstancePlugin(), - }, -}); ----- - -The default subscriptions can be enabled with: - -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - features: { - subscriptions: true - }, -}); ----- - -==== Neo4j GraphQL Subscriptions AMQP package - -The name of the interface underlying the Subscriptions system has changed from `Neo4jGraphQLSubscriptionsPlugin` to `Neo4jGraphQLSubscriptionsEngine`. -If you were previously using the `@neo4j/graphql-plugins-subscriptions-amqp` package, this has been changed to `@neo4j/graphql-amqp-subscriptions-engine` to reflect this underlying change. - -Please uninstall the previous package and install the new one: - -[source, bash, indent=0] ----- -npm uninstall @neo4j/graphql-plugins-subscriptions-amqp -npm install @neo4j/graphql-amqp-subscriptions-engine ----- - -Please then update any imports from: - -[source, javascript, indent=0] ----- -import { Neo4jGraphQLSubscriptionsAMQPPlugin } from "@neo4j/graphql-plugins-subscriptions-amqp"; ----- - -To: - -[source, javascript, indent=0] ----- -import { Neo4jGraphQLAMQPSubscriptionsEngine } from "@neo4j/graphql-amqp-subscriptions-engine"; ----- - -Then change the instantiations from: - -[source, javascript, indent=0] ----- -const plugin = new Neo4jGraphQLSubscriptionsAMQPPlugin({ - connection: { - hostname: "localhost", - username: "guest", - password: "guest", - }, -}); ----- - -To: - -[source, javascript, indent=0] ----- -const subscriptionsEngine = new Neo4jGraphQLAMQPSubscriptionsEngine({ - connection: { - hostname: "localhost", - username: "guest", - password: "guest", - }, -}); ----- - -==== Custom Subscription Plugins - -The underlying subscription system has not changed. -Custom behavior can be implemented the same way, by creating a class implementing the interface described in xref::subscriptions/engines.adoc#custom-subscription[Subscriptions Engines]. - -However, if using TypeScript, the exported interface to implement these classes has been renamed from `Neo4jGraphQLSubscriptionsPlugin` to `Neo4jGraphQLSubscriptionsEngine`. - -== Updated Directives - -We have renamed a number of directives and their arguments, in order to make using `@neo4j/graphql` more intuitive. - -=== `@alias` values are now automatically escaped - -Properties in the alias directive automatically escaped using backticks. If you were using backticks in the `property` argument of your `@alias` directives, you should now remove the escape strings as this is covered by the library. - -[source, graphql, indent=0] ----- -type User { - id: ID! @id - username: String! @alias(property: "dbUserName") -} ----- - -[populatedBy-migration] -=== `@callback` renamed to `@populatedBy` - -Previously, there was ambiguity over the behaviour of `@callback`. As the directive is used to populate a value on input, it has been renamed `@populatedBy` to reflect this. -Additionally, the `name` argument was previously used to specify the callback used to populate the field's value. -This has been renamed to `callback` to make it clear that it refers to a callback. - -Therefore, the following usage of the directive would be invalid: - -[source, graphql, indent=0] ----- -type User { - id: ID! @callback(name: "nanoid", operations: [CREATE]) - firstName: String! - surname: String! -} ----- - -It would instead need to be updated to use the new directive and argument as below: - -[source, graphql, indent=0] ----- -type User { - id: ID! @populatedBy(callback: "nanoid", operations: [CREATE]) - firstName: String! - surname: String! -} ----- - -Configuration for callbacks has also been moved as part of this change. Before these changes, a callback named `nanoid` would need to be defined as below: - -[source, javascript, indent=0] ----- -new Neo4jGraphQL({ - typeDefs, - config: { - callbacks: { - nanoid: () => { return nanoid(); } - } - } -}); ----- - -This has been changed to use the `features` constructor object: - -[source, javascript, indent=0] ----- -new Neo4jGraphQL({ - typeDefs, - features: { - populatedBy: { - callbacks: { - nanoid: () => { return nanoid(); } - } - } - } -}); ----- - -[customResolver-migration] -=== `@computed` renamed to `@customResolver` - -Previously, there was ambiguity over the behaviour of `@computed` and it wasn't clear that it was intended to be used with a custom resolver. In order to make this clear, `@computed` has been renamed to `@customResolver`. -Furthermore, the behaviour of the `from` argument was not clear. The argument is used to specify which fields other fields are required by the custom resolver. As a result, `from` has been renamed to `requires`. - -These changes mean that the following type definition is invalid in version 4.0.0: - -[source, graphql, indent=0] ----- -type User { - firstName: String! - lastName: String! - fullName: String! @computed(from: ["firstName", "lastName"]) -} ----- - -Instead, it would need to be updated to use the new directive and argument as below: - -[source, graphql, indent=0] ----- -type User { - firstName: String! - lastName: String! - fullName: String! @customResolver(requires: ["firstName", "lastName"]) -} ----- - -Note that before and after these changes, a custom resolver would need to be defined as below: - -[source, javascript, indent=0] ----- -new Neo4jGraphQL({ - typeDefs, - resolvers: { - User: { - fullName: ({ firstName, lastName }, args, context, info) => (`${firstName} ${lastName}`), - } - } -}); ----- - -==== `requires` changes - -In version 4.0.0, it is now possible to require non-scalar fields. This means it is also possible to require fields on related type. -To make this possible, the `requires` argument now accept a graphql selection set instead of a list of strings. - -Therefore, the following type definitions: - -[source, graphql, indent=0] ----- -type User { - firstName: String! - lastName: String! - fullName: String! @customResolver(requires: ["firstName", "lastName"]) -} ----- - -Would need to be modified to use a selection set as below: - -[source, graphql, indent=0] ----- -type User { - firstName: String! - lastName: String! - fullName: String! @customResolver(requires: "firstName lastName") -} ----- - -Below is a more advanced example showing what the selection set is capable of: - -[source, graphql, indent=0] ----- -interface Publication { - publicationYear: Int! -} - -type Author { - name: String! - publications: [Publication!]! @relationship(type: "WROTE", direction: OUT) - publicationsWithAuthor: [String!]! - @customResolver( - requires: "name publications { publicationYear ...on Book { title } ... on Journal { subject } }" - ) -} - -type Book implements Publication { - title: String! - publicationYear: Int! - author: [Author!]! @relationship(type: "WROTE", direction: IN) -} - -type Journal implements Publication { - subject: String! - publicationYear: Int! - author: [Author!]! @relationship(type: "WROTE", direction: IN) -} ----- - -Additionally, the requires argument also validates the required selection set against your type definitions. -Therefore, as there is no field called `someFieldThatDoesNotExist`, an error would be thrown on startup if you tried to use the following type definitions: - -[source, graphql, indent=0] ----- -type User { - firstName: String! - lastName: String! - fullName: String! @customResolver(requires: "firstName someFieldThatDoesNotExist") -} ----- - -=== `@cypher` changes -The default behaviour of the `@cypher` directive regarding the translation will change: Instead of using https://neo4j.com/labs/apoc/4.0/overview/apoc.cypher/apoc.cypher.runFirstColumnMany/[apoc.cypher.runFirstColumnMany] it will directly wrap the query within a `CALL { }` subquery. This behvaiour has proven to be much more performant for the same queries, however, it may lead to unexpected changes, mainly when using Neo4j 5.x, where the subqueries need to be _aliased_. - -On top of that, to improve performance, it is recommended to pass the returned alias in the property `columnName`, to ensure the subquery is properly integrated into the larger query. - -For example: - -The graphql query: -[source, graphql, indent=0] ----- -type query { - test: String! @cypher(statement: "RETURN 'hello'") -} ----- - -Would get translated to: -[source,cypher, indent=0] ----- -CALL { - RETURN 'hello' -} -WITH 'hello' AS this -RETURN this ----- - -Which is invalid in Neo4j 5.x. - -To fix it we just need to ensure the `RETURN` elements are aliased: -[source, graphql, indent=0] ----- -type query { - test: String! @cypher(statement: "RETURN 'hello' as result") -} ----- - -This will be a breaking change, but this new behaviour can be used, as an experimental option with the `columnName` flag in the `@cypher` directive: - -[source, graphql, indent=0] ----- -type query { - test: String! @cypher(statement: "RETURN 'hello' as result", columnName: "result") -} ----- - -Additionally, escaping strings is no longer needed. - -=== `@exclude` removed - -The `@exclude` directive has been removed in favor of much more granular configuration directives. - -The new `@query`, `@mutation` and `@subscription` directives instead allow for fully granular configuration for each operation. - -As a direct migration, the following usages are equivalent: - -* `@exclude` and `@query(read: false, aggregate: false) @mutation(operations: []) @subscription(events: [])`. -* `@exclude(operations: [READ])` and `@query(read: false, aggregate: false)`. -* `@exclude(operation: [CREATE, UPDATE, DELETE])` and `@mutation(operations: [])`. - -Whilst there is more verbosity, the directives are significantly more powerful and extensible as the library gains features. - -[full-text-migration] -=== `@fulltext` changes - -In version 4.0.0, a number of improvements have been made to full-text queries. These include the ability to return the full-text score, filter by the score and sorting by the score. - -However, these improvements required a number of breaking changes. - -==== Query changes - -Full-text queries now need to be performed using a top-level query, instead of being performed using an argument on a node query. - -As a result, the following query is now invalid: - -[source, graphql, indent=0] ----- -query { - movies(fulltext: { movieTitleIndex: { phrase: "Some Title" } }) { - title - } -} ----- - -The new top-level queries can be used to return the full-text score, which indicates the confidence of a match, as well as the nodes that have been matched. - -.The new top-level queries accept the following arguments: -* `phrase` which specifies the string to search for in the full-text index. -* `where` which accepts a min/max score as well as the normal filters available on a node. -* `sort` which can be used to sort using the score and node attributes. -* `limit` which is used to limit the number of results to the given integer. -* `offset` which is used to offset by the given number of results. - -The new top-level queries means that for the following type definition: - -[source, graphql, indent=0] ----- -type Movie @fulltext(indexes: [{ indexName: "MovieTitle", fields: ["title"] }]) { # Note that indexName is the new name for the name argument. More about this below. - title: String! -} ----- - -The following top-level query and type definitions would be generated by the library: - -[source, graphql, indent=0] ----- -type Query { - movieFulltextMovieTitle(phrase: String!, where: MovieFulltextWhere, sort: [MovieFulltextSort!], limit: Int, offset: Int): [MovieFulltextResult!]! -} - -"""The result of a fulltext search on an index of Movie""" -type MovieFulltextResult { - score: Float - movies: Movie -} - -"""The input for filtering a fulltext query on an index of Movie""" -input MovieFulltextWhere { - score: FloatWhere - movie: MovieWhere -} - -"""The input for sorting a fulltext query on an index of Movie""" -input MovieFulltextSort { - score: SortDirection - movie: MovieSort -} - -"""The input for filtering the score of a fulltext search""" -input FloatWhere { - min: Float - max: Float -} ----- - -This query can be used to perform a full-text query as below: - -[source, graphql, indent=0] ----- -query { - movieFulltextMovieTitle( - phrase: "Full Metal Jacket", - where: { score: min: 0.4 }, - sort: [{ movie: { title: ASC } }], - limit: 5, - offset: 10 - ) { - score - movies { - title - } - } -} ----- - -The above query would be expected to return results in the following format: - -[source, json, indent=0] ----- -{ - "data": { - "movieFulltextMovieTitle": [ - { - "score": 0.44524085521698, - "movie": { - "title": "Full Moon High" - } - }, - { - "score": 1.411118507385254, - "movie": { - "title": "Full Metal Jacket" - } - } - ] - } -} ----- - -==== Argument changes - -.The following changes have been made to `@fulltext` arguments: -* `queryName` has been added to specify a custom name for the top-level query that is generated. -* `name` has been renamed to `indexName` to avoid ambiguity with the new `queryName` argument. - -These changes means that the following type definition is now invalid: - -[source, graphql, indent=0] ----- -type Movie @fulltext(indexes: [{ name: "MovieTitle", fields: ["title"] }]) { - title: String! -} ----- - -The `name` argument would need to be replaced with `indexName` as below: - -[source, graphql, indent=0] ----- -type Movie @fulltext(indexes: [{ indexName: "MovieTitle", fields: ["title"] }]) { - title: String! -} ----- - -The `queryName` argument can be used as below: - -[source, graphql, indent=0] ----- -type Movie @fulltext(indexes: [{ queryName: "moviesByTitle", indexName: "MovieTitle", fields: ["title"] }]) { - title: String! -} ----- - -This means the top-level query would now be `moviesByTitle` instead of `movieFulltextMovieTitle`: - -[source, graphql, indent=0] ----- -type Query { - moviesByTitle(phrase: String!, where: MovieFulltextWhere, sort: [MovieFulltextSort!], limit: Int, offset: Int): [MovieFulltextResult!]! -} ----- - -=== `@id` changes - -The `@id` directive has been completely pared back in version 4.0.0, with _all_ of its arguments removed. -This has been done to reduce the number of features that this directive was used to toggle, and to ensure that its behaviour is consistent no matter where it is used. - -==== `autogenerate` - -The default value of `autogenerate` was `true`. If this was set to `false`, the `@id` directive was almost a no-op only used to manage a unique node property constraint. Use the `@unique` directive instead. - -==== `global` - -The `global` argument was used to configure the field that would form the global node identifier for Relay. - -This functionality has been moved into its own directive, `@relayId`. The use of `@relayId` will ensure a unique node property constraint for the field. - -==== `unique` - -The `@id` directive used to also manage unique node property constraints for a field. This functionality has now been removed, use the `@unique` directive in combination with `@id` if you want the field to be backed by a constraint. - -=== `@node` changes - -[plural-migration] -==== `plural` argument removed from `@node` and replaced with `@plural` - -How a type name is pluralised has nothing to do with nodes in the database. As a result, having a `plural` argument on the `@node` directive did not make sense. -As a result, the `plural` argument of `@node` has been removed and replaced with a new `@plural` directive. The `@plural` directive takes the pluralised type name using the `value` argument. - -This means that the following type definition is invalid: - -[source, graphql, indent=0] ----- -type Tech @node(label: "TechDB", plural: "Techs") { - name: String -} ----- - -It would need to be updated to use the new directive as below: - -[source, graphql, indent=0] ----- -type Tech @node(label: "TechDB") @plural(value: "Techs") { - name: String -} ----- - -[label-migration] -==== `label` and `additionalLabels` arguments removed from `@node` and replaced with new argument `labels` - -There is no concept of a "main label" in the Neo4j database. As such, keeping these two separate arguments causes a disconnect between the database and the GraphQL library. -As a result, the `label` and `additionalLabels` arguments have been condensed into a single argument `labels` which will accept a list of string labels that used when a node of the given GraphQL type is created. -Please note that defining `labels` means you take control of the database labels of the node. Indexes and constraints in Neo4j only support a single label, for which the first element of the `labels` argument will be used. - -The equivalent of using just the `label` argument is now a list with a single value: - -[source, graphql, indent=0] ----- -type Tech @node(label: "TechDB") { - name: String -} -# becomes -type Tech @node(labels: ["TechDB"]) { - name: String -} ----- - -When creating the equivalent of using just the `additionalLabels` argument now requires the first value in the list to be the GraphQL type name: - -[source, graphql, indent=0] ----- -type Tech @node(additionalLabels: ["TechDB"]) { - name: String -} -# becomes -type Tech @node(labels: ["Tech", "TechDB"]) { - name: String -} ----- - -The equivalent of using both deprecated arguments is a list with all the values concatenated: - -[source, graphql, indent=0] ----- -type Tech @node(label: "TechDB", additionalLabels: ["AwesomeTech"]) { - name: String -} -# becomes -type Tech @node(labels: ["TechDB", "AwesomeTech"]) { - name: String -} ----- - -As before, providing none of these arguments results in the node label being the same as the GraphQL type name. - -Please note the implications on constraints. -In the following example, a unique constraint will be asserted for the label `Tech` and the property `name`: - -[source, graphql, indent=0] ----- -type Tech @node(labels: ["Tech", "TechDB"]) { - name: String @unique -} ----- - -=== `@queryOptions` removed and `limit` argument moved to `@limit` - -If you were using the `@queryOptions` directive to configure the default and max values for limiting the data returned by queries, for instance: - -[source, graphql, indent=0] ----- -type Record @queryOptions(limit: { default: 10, max: 100 }) { - id: ID! -} ----- - -This is now achieved by using the `@limit` directive: - -[source, graphql, indent=0] ----- -type Record @limit(default: 10, max: 100) { - id: ID! -} ----- - -=== `@readonly` and `@writeonly` removed - -The `@readonly` and `@writeonly` directives have been removed in favor of more granular configuration directives. -The new `@selectable` and `@settable` directives can be used to configure not only if fields are readable or writable, but also when they should be readable or writable. - -As a direct migration, the following usages are equivalent: - -* `@readonly` and `@settable(onCreate: false, onUpdate: false)`. -* `@writeonly` and `@selectable(onRead: false, onAggregate: false)`. - -[relationship-aggregate] -=== `@relationship` changes - -==== Relationship types are now automatically escaped - -Relationship types are now automatically escaped. If you have previously escaped your relationship types using backticks, you must now remove these as this is covered by the library. - -==== `aggregate` argument - -In version 4.0.0, the default value of the aggregate argument will be false. -This means that aggregation operation fields will no longer be generated by default when a relationship is defined using the `@relationship` directive. - -For instance, given the following type definitions: - -[source, graphql, indent=0] ----- -type Movie { - title: String! -} - -type Actor { - name: String! - actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) -} ----- - -These will no longer generate `actedInAggregate` for the type `Actor`. - -To enable it, explicitly set the aggregate argument as `true`: - -[source, graphql, indent=0] ----- -type Movie { - title: String! -} - -type Actor { - name: String! - actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, aggregate: true) -} ----- - -=== `@relationshipProperties` now mandatory - -Upcoming changes to interfaces require us to distinguish between interfaces that are used to specify relationship properties, and others. Therefore, the `@relationshipProperties` directive is now required on all relationship property interfaces. -If it is not included, an error will be thrown. - -As a result, in version 4.0.0, the following type definitions are invalid: - -[source, graphql, indent=0] ----- -type Person { - name: String! - movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedIn") -} - -type Movie { - title: String! - actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") -} - -interface ActedIn { - screenTime: Int! -} ----- - -`ActedIn` must be decorated with `@relationshipProperties`: - -[source, graphql, indent=0] ----- -interface ActedIn @relationshipProperties { - screenTime: Int! -} ----- - -== Miscellaneous changes - -=== Duplicate relationship fields are now checked for - -It was possible to define schemas with types that have multiple relationship fields connected by the same type of relationships. Instances of this scenario are now detected during schema generation and an error is thrown so developers are informed to remedy the type definitions. - -An example of what is now considered invalid with these checks: - -[source, graphql, indent=0] ----- -type Team { - player1: Person! @relationship(type: "PLAYS_IN", direction: IN) - player2: Person! @relationship(type: "PLAYS_IN", direction: IN) - backupPlayers: [Person!]! @relationship(type: "PLAYS_IN", direction: IN) -} - -type Person { - teams: [Team!]! @relationship(type: "PLAYS_IN", direction: OUT) -} ----- - -In this example, there are multiple fields in the `Team` type which have the same `Person` type, the same `@relationship` type and ("PLAYS_IN") direction (IN). This is an issue when returning data from the database, as there would be no difference between `player1`, `player2` and `backupPlayers`. Selecting these fields would then return the same data. - -These checks can be disabled by disabling all validation in the library, however, this is not recommended unless in production with 100% confidence of type definitions input. - -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - validate: false, -}); ----- - -[[opt-in-aggregation]] -=== Opt-in Aggregation - -Aggregation operations are no longer generated by default. -They can be enabled case by case using the directives xref::/schema-configuration/type-configuration.adoc#_query[`@query`] and xref::/schema-configuration/field-configuration.adoc#_relationship[`@relationship`]. - -You can enable the operation fields `actorsAggregate` and `actedInAggregate` like this: - -[source, graphql, indent=0] ----- -type Movie { - title: String! -} - -type Actor @query(aggregate: true) { - name: String! - actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, aggregate: true) -} ----- - -=== `cypherParams` - -`cypherParams` is available in the context to provide the ability to pass arbitrary parameters to a custom Cypher query. This functionality remains in 4.0.0, but you no longer have to use the `$cypherParams` prefix to reference these parameters. diff --git a/modules/ROOT/pages/reference/api-reference/index.adoc b/modules/ROOT/pages/reference/api-reference/index.adoc index e27f7a76..67c79342 100644 --- a/modules/ROOT/pages/reference/api-reference/index.adoc +++ b/modules/ROOT/pages/reference/api-reference/index.adoc @@ -3,5 +3,4 @@ :page-aliases: api-reference/index.adoc - xref::reference/api-reference/neo4jgraphql.adoc[`Neo4jGraphQL`] -- xref::reference/api-reference/ogm.adoc[`@neo4j/graphql-ogm`] - +- xref::reference/api-reference/ogm.adoc[`@neo4j/graphql-ogm`] \ No newline at end of file diff --git a/modules/ROOT/pages/reference/api-reference/neo4jgraphql.adoc b/modules/ROOT/pages/reference/api-reference/neo4jgraphql.adoc index 916bb070..ac5692ab 100644 --- a/modules/ROOT/pages/reference/api-reference/neo4jgraphql.adoc +++ b/modules/ROOT/pages/reference/api-reference/neo4jgraphql.adoc @@ -271,4 +271,4 @@ Accepts the arguments below: + Type: `boolean` |Whether or not to create constraints if they do not yet exist. Disabled by default. -|=== +|=== \ No newline at end of file diff --git a/modules/ROOT/pages/reference/api-reference/ogm.adoc b/modules/ROOT/pages/reference/api-reference/ogm.adoc index 2ccb98be..aa63cec2 100644 --- a/modules/ROOT/pages/reference/api-reference/ogm.adoc +++ b/modules/ROOT/pages/reference/api-reference/ogm.adoc @@ -2,4 +2,4 @@ = `@neo4j/graphql-ogm` :page-aliases: api-reference/ogm.adoc -See xref::ogm/reference.adoc[`OGM`]. +See xref::ogm/reference.adoc[`OGM`]. \ No newline at end of file diff --git a/modules/ROOT/pages/reference/directives/cypher.adoc b/modules/ROOT/pages/reference/directives/cypher.adoc deleted file mode 100644 index 390d1351..00000000 --- a/modules/ROOT/pages/reference/directives/cypher.adoc +++ /dev/null @@ -1,229 +0,0 @@ -[[type-definitions-cypher]] -= `@cypher` directive -:page-aliases: type-definitions/cypher.adoc - -The `@cypher` directive binds a GraphQL field to the result(s) of a Cypher query. - -This directive can be used both for properties in a type or as top level queries: - -== Definition - -[source, graphql, indent=0] ----- -"""Instructs @neo4j/graphql to run the specified Cypher statement in order to resolve the value of the field to which the directive is applied.""" -directive @cypher( - """The Cypher statement to run which returns a value of the same type composition as the field definition on which the directive is applied.""" - statement: String!, - """Name of the returned variable from the Cypher statement.""" - columnName: String! -) on FIELD_DEFINITION ----- - - -== Globals - -Global variables are available for use within the Cypher statement. - -=== `this` - -The value `this` is a reference to the currently resolved node, and it can be used to traverse the graph. - -This can be seen in the usage example xref::/type-definitions/directives/cypher.adoc#type-definitions-cypher-object-usage[On an object type field] below. - -=== `auth` - -The value `auth` is represented by the following TypeScript interface definition: - -[source, typescript, indent=0] ----- -interface Auth { - isAuthenticated: boolean; - roles?: string[]; - jwt: any; -} ----- - -For example, you could use the JWT in the request to return the value of the currently logged in User: - -[source, graphql, indent=0] ----- -type User { - id: String -} - -type Query { - me: User @cypher( - statement: """ - MATCH (user:User {id: $jwt.sub}) - RETURN user - """, - columnName: "user" - ) -} ----- - - -=== `cypherParams` -Use to inject values into the cypher query from the GraphQL context function. - -Inject into context: - -[source, typescript, indent=0] ----- -const server = new ApolloServer({ - typeDefs, -}); - -await startStandaloneServer(server, { - context: async ({ req }) => ({ cypherParams: { userId: "user-id-01" } }), -}); ----- - -Use in cypher query: - -[source, graphql, indent=0] ----- -type Query { - userPosts: [Post] @cypher(statement: """ - MATCH (:User {id: $userId})-[:POSTED]->(p:Post) - RETURN p - """, columnName: "p") -} ----- - -== Return values - -The return value of the Cypher statement must be of the same type to which the directive is applied. - -The variable should also be aliased with a name that must be the same as the named passed to `columnName`, this can be the name of a node or relationship query or an alias in the `RETURN` statement of the cypher. - -=== Scalar values - -The Cypher statement must return a value which matches the scalar type to which the directive was applied. - -[source, graphql, indent=0] ----- -type Query { - randomNumber: Int @cypher(statement: "RETURN rand() as result", columnName: "result") -} ----- - -=== Object types - -When returning an object type, all fields of the type must be available in the Cypher return value. This can be achieved by either returning the entire object from the Cypher query, or returning a map of the fields which are required for the object type. Both approaches are demonstrated below: - -[source, graphql, indent=0] ----- -type User { - id -} - -type Query { - users: [User] - @cypher( - statement: """ - MATCH (u:User) - RETURN u - """, - columnName: "u" - ) -} ----- - -[source, graphql, indent=0] ----- -type User { - id -} - -type Query { - users: [User] @cypher(statement: """ - MATCH (u:User) - RETURN { - id: u.id - } as result - """, columnName: "result") -} ----- - -The downside of the latter approach is that you will need to adjust the return object as you change your object type definition. - -== Usage examples - -[[type-definitions-cypher-object-usage]] -=== On an object type field - -In the example below, a field `similarMovies` is bound to the `Movie` type, to find other movies with an overlap of actors: - -[source, graphql, indent=0] ----- -type Actor { - actorId: ID! - name: String - movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) -} - -type Movie { - movieId: ID! - title: String - description: String - year: Int - actors(limit: Int = 10): [Actor!]! - @relationship(type: "ACTED_IN", direction: IN) - similarMovies(limit: Int = 10): [Movie] - @cypher( - statement: """ - MATCH (this)<-[:ACTED_IN]-(:Actor)-[:ACTED_IN]->(rec:Movie) - WITH rec, COUNT(*) AS score ORDER BY score DESC - RETURN rec LIMIT $limit - """, - columnName: "rec" - ) -} ----- - -=== On a query type field - -The example below demonstrates a simple query to return all of the actors in the database: - -[source, graphql, indent=0] ----- -type Actor { - actorId: ID! - name: String -} - -type Query { - allActors: [Actor] - @cypher( - statement: """ - MATCH (a:Actor) - RETURN a - """, - columnName: "a" - ) -} ----- - -=== On a mutation type field - -The example below demonstrates a simple mutation using a Cypher query to insert a single actor with the specified name argument: - -[source, graphql, indent=0] ----- -type Actor { - actorId: ID! - name: String -} - -type Mutation { - createActor(name: String!): Actor - @cypher( - statement: """ - CREATE (a:Actor {name: $name}) - RETURN a - """, - columnName: "a" - ) -} ----- \ No newline at end of file diff --git a/modules/ROOT/pages/reference/directives/default-values.adoc b/modules/ROOT/pages/reference/directives/default-values.adoc deleted file mode 100644 index 97c72d29..00000000 --- a/modules/ROOT/pages/reference/directives/default-values.adoc +++ /dev/null @@ -1,96 +0,0 @@ -[[type-definitions-default-values]] -= Default Values - -[[type-definitions-default-values-default]] -== `@default` - -When generating the input type for the create mutation, the value specified in this directive will be used as the default value for this field. - -=== Definition - -[source, graphql, indent=0] ----- -"""Int | Float | String | Boolean | ID | DateTime | Enum""" -scalar Scalar - -"""Instructs @neo4j/graphql to set the specified value as the default value in the CreateInput type for the object type in which this directive is used.""" -directive @default( - """The default value to use. Must be a scalar type and must match the type of the field with which this directive decorates.""" - value: Scalar!, -) on FIELD_DEFINITION ----- - -=== Usage with Enums - -`@default` may be used with enums. When setting the default value for an enum field, -the value must be one of the enumerated enum values. - -[source, graphql, indent=0] ----- -enum Location { - HERE - THERE - EVERYWHERE -} - -type SomeType { - firstLocation: Location! @default(value: HERE) # valid usage - secondLocation: Location! @default(value: ELSEWHERE) # invalid usage, will throw an error -} ----- - -[[type-definitions-default-values-coalesce]] -== `@coalesce` - -When translating from GraphQL to Cypher, any instances of fields to which this directive is applied will be wrapped in a `coalesce()` function in the WHERE clause (see https://neo4j.com/developer/kb/understanding-non-existent-properties-and-null-values/#_use_coalesce_to_use_a_default_for_a_null_value). This helps to query against non-existent properties in a database, however it is encouraged to populate these properties with meaningful values if this is becoming the norm. This is a very primitive implementation of the function which only takes a static default value as opposed to using another property in a node or a Cypher expression. - -=== Definition - -[source, graphql, indent=0] ----- -"""Int | Float | String | Boolean | ID | DateTime | Enum""" -scalar ScalarOrEnum - -"""Instructs @neo4j/graphql to wrap the property in a coalesce() function during queries, using the single value specified.""" -directive @coalesce( - """The value to use in the coalesce() function. Must be a scalar type and must match the type of the field with which this directive decorates.""" - value: Scalar!, -) on FIELD_DEFINITION ----- - -=== Usage with Enums - -`@coalesce` may be used with enums. When setting the default value for an enum field, -the value must be one of the enumerated enum values. - -[source, graphql, indent=0] ----- -enum Status { - ACTIVE - INACTIVE -} -type Movie { - status: Status @coalesce(value: ACTIVE) -} ----- - -[[type-definitions-default-values-limit]] -== `@limit` - -The `@limit` is to be used on nodes, where applied will inject values into a query such as the `limit`. - -=== Definition - -[source, graphql, indent=0] ----- -"""The `@limit` is to be used on nodes, where applied will inject values into a query such as the `limit`.""" -directive @limit( - default: Int - max: Int -) on OBJECT ----- - -The directive has two arguments: - -* `default` - If no `limit` argument is passed to the query, the default limit will be used. The query may still pass a higher or lower `limit`. -* `max` - Defines the maximum limit to be passed to the query. If a higher value is passed, this will be used instead. If no `default` value is set, `max` will be used for queries without limit. \ No newline at end of file diff --git a/modules/ROOT/pages/reference/directives/index.adoc b/modules/ROOT/pages/reference/directives/index.adoc deleted file mode 100644 index 89535425..00000000 --- a/modules/ROOT/pages/reference/directives/index.adoc +++ /dev/null @@ -1,161 +0,0 @@ -[[directives]] -= Directives -:page-aliases: directives.adoc - -== `@alias` - -The `@alias` directive will map a GraphQL schema field to a Neo4j property on a node or relationship. - -Reference: xref::/type-definitions/directives/database-mapping.adoc#type-definitions-alias[`@alias`] - -== `@coalesce` - -The `@coalesce` directive exposes a mechanism for querying against non-existent, `null` values on a node. - -Reference: xref::/type-definitions/directives/default-values.adoc#type-definitions-default-values-coalesce[`@coalesce`] - -[[custom-resolver-directive]] -== `@customResolver` - -The `@customResolver` directive specifies that a field will be resolved by a custom resolver, and allows the specification -of any required fields that will be passed as arguments to the custom resolver. - -Reference: xref::custom-resolvers.adoc#custom-resolver-directive[`@customResolver`] - -== `@cypher` - -The `@cypher` directive overrides field resolution (including `Query` and `Mutation` fields), instead resolving with the specified Cypher. - -Reference: xref::/type-definitions/directives/cypher.adoc[`@cypher` directive] - -== `@default` - -The `@default` directive allows for the setting of a default value for a field on object creation. - -Reference: xref::/type-definitions/directives/default-values.adoc#type-definitions-default-values-default[`@default`] - -== `@exclude` label:deprecated[] - -This directive is deprecated. - -Use the xref:schema-configuration/type-configuration.adoc#_query[`@query`], xref:schema-configuration/type-configuration.adoc#_mutation[`@mutation`] and the xref:schema-configuration/type-configuration.adoc#_subscription[`@subscription`] directives instead. - -The `@exclude` directive is used on object types to instruct them to be skipped during Query, Mutation and Subscription generation. - -Reference: xref::schema-configuration/type-configuration.adoc#_exclude_deprecated[`@exclude`] - -== `@filterable` - -The `@filterable` directive defines the filters generated for a field. - -Reference: xref:schema-configuration/field-configuration.adoc#_filterable[`@filterable`] - -== `@fulltext` - -The `@fulltext` directive indicates that there should be a Fulltext index inserted into the database for the specified Node and its properties. - -Reference: xref::/type-definitions/directives/indexes-and-constraints.adoc#type-definitions-indexes-fulltext[Fulltext indexes] - -== `@id` - -The `@id` directive marks a field as the unique ID for an object type, and allows for autogeneration of IDs. - -Reference: xref::/type-definitions/directives/autogeneration.adoc#type-definitions-autogeneration-id[`@id`] - -== `@limit` - -The `@limit` is to be used on nodes, and when applied will inject values into Cypher `LIMIT` clauses. - -Reference: xref::/type-definitions/directives/default-values.adoc#type-definitions-default-values-limit[`@limit`] - -== `@mutation` - -This directive is used to limit the availability of Mutation operations in the library. - -Reference: xref:schema-configuration/type-configuration.adoc#_mutation[`@mutation`] - -== `@node` - -The `@node` directive is used to specify the configuration of a GraphQL object type which represents a Neo4j node. - -Reference: xref::/type-definitions/directives/database-mapping.adoc#type-definitions-node[`@node`] - -[[plural-directive]] -== `@plural` - -The `@plural` directive redefines how to compose the plural of the type for the generated operations. -This is particularly useful for types that are not correctly pluralized or are non-English words. - -Reference: xref::/type-definitions/directives/database-mapping.adoc#type-definitions-plural[`@plural`] - -[[populated-by-directive]] -== `@populatedBy` - -The `@populatedBy` directive is used to specify a callback function that gets executed during GraphQL query parsing, -to populate fields which have not been provided within the input. - -Reference: xref::/type-definitions/directives/autogeneration.adoc#type-definitions-autogeneration-populated-by[`@populatedBy`] - -== `@private` - -The `@private` directive protects fields which should only be available through the xref::ogm/index.adoc[OGM]. - -Reference: xref::ogm/private.adoc[`@private` Directive] - -== `@query` - -This directive is used to limit the availability of Query operations in the library. - -Reference: xref:schema-configuration/type-configuration.adoc#_query[`@query`] - -== `@relationship` - -The `@relationship` directive is used to configure relationships between object types. - -Reference: xref::/type-definitions/types/relationships.adoc[Relationships], xref::schema-configuration/field-configuration.adoc#_relationship[`@relationship`] - -== `@relationshipProperties` - -Required to help you distinguish between interfaces which are used for relationship properties, and otherwise. - -Can only be used on interfaces, as per its definition: - -[source, graphql, indent=0] ----- -"""Required to differentiate between interfaces for relationship properties, and otherwise.""" -directive @relationshipProperties on INTERFACE ----- - -== `@relayId` - -The `@relayId` directive can be used on object type fields, to flag which field should be used as the global node identifier for Relay. Can be used once per type. The use of the `@relayId` directive ensures a unique node property constraint for the field. - -== `@selectable` - -The `@selectable` directive sets the availability of fields on queries and aggregations. - -Reference: xref:schema-configuration/field-configuration.adoc#_selectable[`@selectable`] - -== `@settable` - -The `@settable` directive sets the availability of fields on the create and update inputs. - -Reference: xref:schema-configuration/field-configuration.adoc#_settable[`@settable`] - -== `@subscription` - -This directive is used to limit Subscription events available in the library. - -Reference: xref:schema-configuration/type-configuration.adoc#_subscription[`@subscription`] - -== `@timestamp` - -The `@timestamp` directive flags fields to be used to store timestamps on create/update events. - -Reference: xref::/type-definitions/directives/autogeneration.adoc#type-definitions-autogeneration-timestamp[`@timestamp`] - -== `@unique` - -The `@unique` directive indicates that there should be a uniqueness constraint in the database for the fields that it is applied to. - -Reference: xref::/type-definitions/directives/indexes-and-constraints.adoc#type-definitions-constraints-unique[Unique node property constraints] diff --git a/modules/ROOT/pages/reference/directives/schema-configuration/type-configuration.adoc b/modules/ROOT/pages/reference/directives/schema-configuration/type-configuration.adoc deleted file mode 100644 index 0d0a5e46..00000000 --- a/modules/ROOT/pages/reference/directives/schema-configuration/type-configuration.adoc +++ /dev/null @@ -1,156 +0,0 @@ -[[schema-configuration-type-configuration]] -= Type Configuration - - -When representing a Neo4j Node, a GraphQL Object type produces multiple operation fields in the `Query`, `Mutation`, and `Subscription` types. -For example: - -[source, graphql, indent=0] ----- -type Movie { - title: String - length: Int -} ----- - -From these type definitions, the library generates the following operation fields: - -**Query**: - - * `movies` - * `moviesAggregate` - * `moviesConnection` - -**Mutation**: - - * `createMovies` - * `deleteMovies` - * `updateMovies` - -**Subscription**: - - * `movieCreated` - * `movieUpdated` - * `movieDeleted` -. - -This section explains how to reduce the operation fields produced using the directives `@query`, `@mutation`, and `@subscription`. - -== `@query` - -This directive is used to limit the availability of query operations in the library. - -=== Definition - -[source, graphql, indent=0] ----- -directive @query(read: Boolean! = true, aggregate: Boolean! = false) on OBJECT | SCHEMA ----- - -[NOTE] -==== -Aggregations will no longer be generated by default in the 4.0.0 version of the library. -See xref:migration/v4-migration/index.adoc#opt-in-aggregation[`Opt-in Aggregation`] for more information. -==== - -=== Usage - -==== Disable _movies_ and _moviesConnection_ operations - -[source, graphql, indent=0] ----- -type Movie @query(read: false, aggregate: true) { - title: String - length: Int -} ----- - -==== Disable _moviesAggregate_ operations - -[source, graphql, indent=0] ----- -type Movie @query(read: true, aggregate: false) { - title: String - length: Int -} ----- - -== `@mutation` - -This directive is used to limit the availability of Mutation operations in the library. - -=== Definition - -[source, graphql, indent=0] ----- -enum MutationFields { - CREATE - UPDATE - DELETE -} - -directive @mutation(operations: [MutationFields!]! = [CREATE, UPDATE, DELETE]) on OBJECT | SCHEMA ----- - -=== Usage - -==== Disable Create, Delete, and Update operations for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @mutation(operations: []) { - title: String - length: Int -} ----- - -==== Enable only Create operations for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @mutation(operations: [CREATE]) { - title: String - length: Int -} ----- - -== `@subscription` - -This directive is used to limit Subscription operations in the library. - -=== Definition - -[source, graphql, indent=0] ----- -enum SubscriptionEvent { - CREATED - UPDATED - DELETED - RELATIONSHIP_CREATED - RELATIONSHIP_DELETED -} - -directive @subscription(events: [SubscriptionEvent!]! = [CREATED, UPDATED, DELETED, RELATIONSHIP_CREATED, RELATIONSHIP_DELETED]) on OBJECT | SCHEMA ----- - -=== Usage - -==== Disable subscriptions for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @subscription(events: []) { - title: String - length: Int -} ----- - -==== Enable only _movieCreated_ subscription for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @subscription(events: [CREATED]) { - title: String - length: Int -} ----- diff --git a/modules/ROOT/pages/reference/type-definitions/interfaces.adoc b/modules/ROOT/pages/reference/type-definitions/interfaces.adoc deleted file mode 100644 index adc72a27..00000000 --- a/modules/ROOT/pages/reference/type-definitions/interfaces.adoc +++ /dev/null @@ -1,135 +0,0 @@ -[[schema-configuration-type-configuration]] -= Type Configuration - - -When representing a Neo4j Node, a GraphQL Object type produces multiple operation fields in the `Query`, `Mutation`, and `Subscription` types. -For example: - -[source, graphql, indent=0] ----- -type Movie { - title: String - length: Int -} ----- - -From these type definitions, the library generates the following operation fields: - -**Query**: - - * `movies` - * `moviesAggregate` - * `moviesConnection` - -**Mutation**: - - * `createMovies` - * `deleteMovies` - * `updateMovies` - -**Subscription**: - - * `movieCreated` - * `movieUpdated` - * `movieDeleted` -. - -This section explains how to reduce the operation fields produced using the directives `@query`, `@mutation`, and `@subscription`. - -== `@query` - -This directive is used to limit the availability of query operations in the library. - -=== Definition - -[source, graphql, indent=0] ----- -directive @query(read: Boolean! = true, aggregate: Boolean! = false) on OBJECT | SCHEMA ----- - -[NOTE] -==== -Aggregations will no longer be generated by default in the 4.0.0 version of the library. -See xref:migration/v4-migration/index.adoc#opt-in-aggregation[`Opt-in Aggregation`] for more information. -==== - -[[type-definitions-interfaced-types-querying]] -== Querying an interface - -Which implementations are returned by a query are dictated by the `where` filter applied. - -For example, the following will return all productions with title starting "The " for every actor: - -[source, graphql, indent=0] ----- -enum MutationFields { - CREATE - UPDATE - DELETE -} - -directive @mutation(operations: [MutationFields!]! = [CREATE, UPDATE, DELETE]) on OBJECT | SCHEMA ----- - -=== Usage - -==== Disable Create, Delete, and Update operations for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @mutation(operations: []) { - title: String - length: Int -} ----- - -==== Enable only Create operations for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @mutation(operations: [CREATE]) { - title: String - length: Int -} ----- - -== `@subscription` - -This directive is used to limit Subscription operations in the library. - -=== Definition - -[source, graphql, indent=0] ----- -enum SubscriptionFields { - CREATE - UPDATE - DELETE - CREATE_RELATIONSHIP - DELETE_RELATIONSHIP -} - -directive @subscription(operations: [SubscriptionFields!]! = [CREATE, UPDATE, DELETE, CREATE_RELATIONSHIP, DELETE_RELATIONSHIP]) on OBJECT | SCHEMA ----- - -=== Usage - -==== Disable subscriptions for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @subscription(operations: []) { - title: String - length: Int -} ----- - -==== Enable only _movieCreated_ subscription for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @subscription(operations: [CREATE]) { - title: String - length: Int -} ----- \ No newline at end of file diff --git a/modules/ROOT/pages/subscriptions/events.adoc b/modules/ROOT/pages/subscriptions/events.adoc index ec6a94ea..5f7516ef 100644 --- a/modules/ROOT/pages/subscriptions/events.adoc +++ b/modules/ROOT/pages/subscriptions/events.adoc @@ -11,7 +11,7 @@ This page covers a variety of subscription options offered by the Neo4j GraphQL [NOTE] ==== Only changes made through `@neo4j/graphql` should trigger the events here described. -Changes made directly to the database or using the xref::reference/directives/cypher.adoc[`@cypher` directive] will **not** trigger any event. +Changes made directly to the database or using the xref::type-definitions/directives/cypher/[`@cypher` directive] will **not** trigger any event. ==== From a58594bf15ef5208a12bca12a86d8dcfbff3fa4e Mon Sep 17 00:00:00 2001 From: Neil Dewhurst Date: Mon, 4 Sep 2023 10:56:28 +0100 Subject: [PATCH 09/28] Update publish.yml --- publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/publish.yml b/publish.yml index 3a563497..1cffde5d 100644 --- a/publish.yml +++ b/publish.yml @@ -7,7 +7,7 @@ site: content: sources: - url: https://github.com/neo4j/docs-graphql.git - branches: ['dev', '3.x'] + branches: ['4.x', '3.x'] exclude: - '!**/_includes/*' - '!**/readme.adoc' From d35c7296ab2660a499d3f8c702ae87d18a897608 Mon Sep 17 00:00:00 2001 From: Lidia Zuin <102308961+lidiazuin@users.noreply.github.com> Date: Fri, 15 Sep 2023 11:12:11 +0200 Subject: [PATCH 10/28] Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst --- .../ROOT/pages/migration/authorization.adoc | 4 + modules/ROOT/pages/migration/index.adoc | 7 + modules/ROOT/pages/migration/ogm.adoc | 5 + .../ROOT/pages/migration/v2-migration.adoc | 606 ++++++++++ .../ROOT/pages/migration/v3-migration.adoc | 253 ++++ .../pages/migration/v4-migration/index.adoc | 1015 +++++++++++++++++ 6 files changed, 1890 insertions(+) create mode 100644 modules/ROOT/pages/migration/v2-migration.adoc create mode 100644 modules/ROOT/pages/migration/v3-migration.adoc create mode 100644 modules/ROOT/pages/migration/v4-migration/index.adoc diff --git a/modules/ROOT/pages/migration/authorization.adoc b/modules/ROOT/pages/migration/authorization.adoc index 561b6f04..2ab125de 100644 --- a/modules/ROOT/pages/migration/authorization.adoc +++ b/modules/ROOT/pages/migration/authorization.adoc @@ -1,6 +1,10 @@ = Authentication and Authorization +<<<<<<< HEAD:modules/ROOT/pages/migration/authorization.adoc :description: This page describes the changes in authentication and authorization features in version 4.0.0 of the Neo4j GraphQL Library. :page-aliases: auth/global-authentication.adoc, migration/v4-migration/authorization.adoc +======= +:page-aliases: auth/global-authentication.adoc +>>>>>>> e533fd8 (Add page-aliases for version 4 (#31) (#32)):modules/ROOT/pages/migration/v4-migration/authorization.adoc The largest breaking change in version 4.0.0 is the removal of the `@auth` directive, which requires a migration to the new `@authentication`, `@authorization`, and `@subscriptionsAuthorization` directives. diff --git a/modules/ROOT/pages/migration/index.adoc b/modules/ROOT/pages/migration/index.adoc index 72b159bb..f4876bb9 100644 --- a/modules/ROOT/pages/migration/index.adoc +++ b/modules/ROOT/pages/migration/index.adoc @@ -1,7 +1,14 @@ +<<<<<<< HEAD [[v4-migration]] :description: This page lists the breaking changes from version 3.0.0 to 4.0.0 and describes how to update. :page-aliases: guides/v4-migration/index.adoc , migration/v4-migration/index.adoc = Migration to 4.0.0 +======= +[[migration-guide]] += Migration from `neo4j-graphql-js` +:page-aliases: guides/index.adoc, guides/migration-guide/index.adoc, guides/migration-guide/server.adoc, guides/migration-guide/type-definitions.adoc, guides/migration-guide/mutations.adoc + +>>>>>>> e533fd8 (Add page-aliases for version 4 (#31) (#32)) This page lists all breaking changes from the Neo4j GraphQL Library version 3.x to 4.x and how to update it. diff --git a/modules/ROOT/pages/migration/ogm.adoc b/modules/ROOT/pages/migration/ogm.adoc index 28e2c9aa..7b36a6b3 100644 --- a/modules/ROOT/pages/migration/ogm.adoc +++ b/modules/ROOT/pages/migration/ogm.adoc @@ -1,6 +1,11 @@ = OGM +<<<<<<< HEAD:modules/ROOT/pages/migration/ogm.adoc :description: This page describes what updates were made to the OGM tool in version 4.0.0 of the Neo4j GraphQL Library. :page-aliases: guides/v4-migration/ogm.adoc, migration/v4-migration/ogm.adoc +======= +:page-aliases: guides/v4-migration/ogm.adoc + +>>>>>>> e533fd8 (Add page-aliases for version 4 (#31) (#32)):modules/ROOT/pages/migration/v4-migration/ogm.adoc This page describes what updates were made to the OGM tool in version 4.0.0 of the Neo4j GraphQL Library. diff --git a/modules/ROOT/pages/migration/v2-migration.adoc b/modules/ROOT/pages/migration/v2-migration.adoc new file mode 100644 index 00000000..a6d93ec6 --- /dev/null +++ b/modules/ROOT/pages/migration/v2-migration.adoc @@ -0,0 +1,606 @@ +[[v2-migration]] += 2.0.0 Migration +:page-aliases: guides/v2-migration/index.adoc, guides/v2-migration/miscellaneous.adoc, guides/v2-migration/unions.adoc, guides/v2-migration/mutations.adoc + + +Version 2.0.0 of `@neo4j/graphql` adds support for relationship properties, with some breaking changes to facilitate these new features. All of the required changes will be on the client side, and this guide will walk through what has changed. + +== How to Upgrade + +Simply update `@neo4j/graphql` using npm or your package manager of choice: + +[source, bash, indent=0] +---- +npm update @neo4j/graphql +---- + +From this point on, it is primarily Mutations which will form the bulk of the migration. + +[[v2-migration-mutations]] +== Mutations + +The most broadly affected area of functionality by the 2.0.0 upgrade are the nested operations of Mutations, to facilitate the mutation of and filtering on relationship properties. + +The examples in this section will be based off the following type definitions: + +[source, graphql, indent=0] +---- +type Actor { + name: String! + movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) +} + +type Movie { + title: String! + actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) +} +---- + +The theme that you will notice during this section is that as a general rule of thumb, a `node` field will need adding to your inputs where it will also be possible to filter on relationship properties. + +[[v2-migration-mutations-create]] +=== Create + +Focussing on the `createMovies` mutation, notice that the definition of the `createMovies` mutation is unchanged: + +[source, graphql, indent=0] +---- +input MovieCreateInput { + title: String! + actors: MovieActorsFieldInput +} + +type Mutation { + createMovies(input: [MovieCreateInput!]!): CreateMoviesMutationResponse! +} +---- + +There are no changes to any of the arguments or types at this level. However, within its nested operations, type modifications have taken place to allow for relationship properties. + +In practice, take a mutation that creates the film "The Dark Knight" and then: + +* Creates a new actor "Heath Ledger" +* Connects to the existing actor "Christian Bale" + +In the previous version of the library, this would have looked like this: + +[source, graphql, indent=0] +---- +mutation { + createMovies( + input: [ + { + title: "The Dark Knight" + actors: { + create: [ + { + name: "Heath Ledger" + } + ] + connect: [ + { + where: { + name: "Christian Bale" + } + } + ] + } + } + ] + ) { + movies { + title + } + } +} +---- + +This will now have to look like this in order to function in the same way: + +[source, graphql, indent=0] +---- +mutation { + createMovies( + input: [ + { + title: "The Dark Knight" + actors: { + create: [ + { + node: { + name: "Heath Ledger" + } + } + ] + connect: [ + { + where: { + node: { + name: "Christian Bale" + } + } + } + ] + } + } + ] + ) { + movies { + title + } + } +} +---- + +Note the additional level "node" before specifying the actor name for the create operation and in the connect where. This additional level allows for the setting of relationship properties for the new relationship, and filtering on existing relationship properties when looking for the node to connect to. See the page xref::mutations/index.adoc[mutations] for details on this. + +=== Update + +Focussing on the `updateMovies` mutation, notice that the definition of the `updateMovies` mutation is unchanged: + +[source, graphql, indent=0] +---- +type Mutation { + updateMovies( + where: MovieWhere + update: MovieUpdateInput + connect: MovieConnectInput + disconnect: MovieDisconnectInput + create: MovieRelationInput + delete: MovieDeleteInput + ): UpdateMoviesMutationResponse! +} +---- + +The `create` and `connect` nested operations are primarily the same as in the `createMovies` mutation, so please see the <> section for the difference for these operations. + +The `delete` nested operation is primarily the same as in the `deleteMovies` mutation, so please see the <> section for that. + +==== Update + +For example, say that you accidentally misspelt Christian Bale's surname and wanted to fix that. In the previous version, you might have achieved that by: + +[source, graphql, indent=0] +---- +mutation { + updateMovies( + where: { + title: "The Dark Knight" + } + update: { + actors: [ + { + where: { + name_ENDS_WITH: "Bail" + } + update: { + name: "Christian Bale" + } + } + ] + } + ) { + movies { + title + actors { + name + } + } + } +} +---- + +This will now have to look like this in order to function in the same way: + +[source, graphql, indent=0] +---- +mutation { + updateMovies( + where: { + title: "The Dark Knight" + } + update: { + actors: [ + { + where: { + node: { + name_ENDS_WITH: "Bail" + } + } + update: { + node: { + name: "Christian Bale" + } + } + } + ] + } + ) { + movies { + title + actors { + name + } + } + } +} +---- + +Note the added layer of abstraction of `node` in both the `where` and `update` clauses. + +==== Disconnect + +For example, say you mistakenly put Ben Affleck as playing the role of Batman in "The Dark Knight", and you wanted to disconnect those nodes. In the previous version, this would have looked like: + +[source, graphql, indent=0] +---- +mutation { + updateMovies( + where: { + title: "The Dark Knight" + } + disconnect: { + actors: [ + { + where: { + name: "Ben Affleck" + } + } + ] + } + ) { + movies { + title + actors { + name + } + } + } +} +---- + +This will now have to look like this in order to function in the same way: + +[source, graphql, indent=0] +---- +mutation { + updateMovies( + where: { + title: "The Dark Knight" + } + disconnect: { + actors: [ + { + where: { + node: { + name: "Ben Affleck" + } + } + } + ] + } + ) { + movies { + title + actors { + name + } + } + } +} +---- + +[[v2-migration-mutations-delete]] +=== Delete + +Focussing on the `deleteMovies` mutation, notice that the definition of the `deleteMovies` mutation is unchanged: + +[source, graphql, indent=0] +---- +input MovieDeleteInput { + actors: [MovieActorsDeleteFieldInput!] +} + +type Mutation { + deleteMovies(where: MovieWhere, delete: MovieDeleteInput): DeleteInfo! +} +---- + +There are no changes to any of the arguments or types at this level, but there are some details to note in the `MovieActorsDeleteFieldInput` type. + +Previously, you would have expected this to look like: + +[source, graphql, indent=0] +---- +input MovieActorsDeleteFieldInput { + delete: ActorDeleteInput + where: ActorWhere +} +---- + +This allowed you to filter on fields of the `Actor` type and delete based on that. However, following this upgrade, you will find: + +[source, graphql, indent=0] +---- +input MovieActorsDeleteFieldInput { + delete: ActorDeleteInput + where: MovieActorsConnectionWhere +} +---- + +This means that not only can you filter on node properties, but also relationship properties, in order to find and delete `Actor` nodes. + +In practice, a mutation that deletes the film "The Dark Knight" and the related actor "Christian Bale" would have previously looked like this: + +[source, graphql, indent=0] +---- +mutation { + deleteMovies( + where: { + title: "The Dark Knight" + } + delete: { + actors: { + where: { + name: "Christian Bale" + } + } + } + ) { + nodesDeleted + relationshipsDeleted + } +} +---- + +This will now have to look like this in order to function in the same way: + +[source, graphql, indent=0] +---- +mutation { + deleteMovies( + where: { + title: "The Dark Knight" + } + delete: { + actors: { + where: { + node: { + name: "Christian Bale" + } + } + } + } + ) { + nodesDeleted + relationshipsDeleted + } +} +---- + +Note the additional level "node" before specifying the actor name. + +[[v2-migration-unions]] +== Unions + +In this release, the decision was made to take the opportunity to overhaul the existing support for unions on relationship fields, laying down the foundations for adding top-level union support in the future. + +All examples in this section will be based off the following type definitions: + +[source, graphql, indent=0] +---- +type Actor { + name: String! + actedIn: [Production!]! @relationship(type: "ACTED_IN", direction: OUT) +} + +type Movie { + title: String! + actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) +} + +type Series { + title: String! + actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) +} + +union Production = Movie | Series +---- + +=== Input types + +The structure of input types for union queries and mutations have been changed for user friendliness, and a more consistent API. + +Essentially, field names which were previously of template `_` (for example, "actedIn_Movie") are now an object, with the field name at the top, and the member types under it. + +For example, a Mutation which would have previously been: + +[source, graphql, indent=0] +---- +mutation { + createActors( + input: [ + { + name: "Tom Hiddleston" + actedIn_Movie: { + create: [ + { + title: "The Avengers" + } + ] + } + actedIn_Series: { + create: [ + { + title: "Loki" + } + ] + } + } + ] + ) +} +---- + +Will now be: + +[source, graphql, indent=0] +---- +mutation { + createActors( + input: [ + { + name: "Tom Hiddleston" + actedIn: { + Movie: { + create: [ + { + node: { + title: "The Avengers" + } + } + ] + } + Series: { + create: [ + { + node: { + title: "Loki" + } + } + ] + } + } + } + ] + ) +} +---- + +Note the change in structure for union input, but also the additional `node` level which enables the use of relationship properties. These changes are consistent across all operations, including `where`. + +=== Filtering union fields + +There has been a slight change to how you filter union fields, adding a `where` level above each union member. For example, for a query which would have used to have looked like: + +[source, graphql, indent=0] +---- +query { + actors { + name + actedIn(Movie: { "The Avengers" }) { + ... on Movie { + title + } + } + } +} +---- + +This will now be written like: + +[source, graphql, indent=0] +---- +query { + actors { + name + actedIn(where: { Movie: { "The Avengers" }}) { + ... on Movie { + title + } + } + } +} +---- + +Furthermore, the where argument used now dictates which union members are returned from the database, to prevent overfetching. Please see xref::troubleshooting.adoc#appendix-preventing-overfetching[this page] for background and explanation of this decision. + +[[v2-migration-miscellaneous]] +== Miscellaneous + +=== `skip` renamed to `offset` + +In the release of Apollo Client 3.0, it became a bit more opinionated about pagination, favouring `offset` and `limit` over `skip` and `limit`. Acknowledging that the majority of users will be using Apollo Client 3.0, the page-based pagination arguments have been updated to align with this change. + +For example, fetching page 3 of pages of 10 movies would have looked like the following in version `1.x`: + +[source, graphql, indent=0] +---- +query { + movies(options: { skip: 20, limit: 10 }) { + title + } +} +---- + +This will now need to queried as follows: + +[source, graphql, indent=0] +---- +query { + movies(options: { offset: 20, limit: 10 }) { + title + } +} +---- + +=== Count queries + +Whilst not a necessary migration step, if you are using page-based pagination, it's important to note the addition of count queries in version 2.0.0. These will allow you to calculate the total number of pages for a particular filter, allowing you to implement much more effective pagination. + +== Schema validation + +In version 2.0.0, there are greater levels of schema validation. However, upon upgrading, you might find that validation is too strict (for example if using certain generated types in your definitions). You can temporarily disable this new validation on construction: + +[source, javascript, indent=0] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, + config: { + skipValidateTypeDefs: true, + }, +}) +---- + +If you need to do this, please report the scenario as an issue on GitHub. + +=== `_IN` and `_NOT_IN` filters on relationships removed + +There were previously `_IN` and `_NOT_IN` filters for one-to-many and one-to-one relationships, but these were surplus to requirements, and didn't match for all cardinalities (many-to-many relationships don't have `_INCLUDES` and `_NOT_INCLUDES`). These may be added back in the future if and when we look more holistically at distinguishing between different relationship cardinalities. + +You can still achieve identical filters through different routes. For example, if you had the following schema: + +[source, graphql, indent=0] +---- +type Movie { + title: String! + director: Director! @relationship(type: "DIRECTED", direction: IN) +} + +type Director { + name: String! + movies: [Movie!]! @relationship(type: "DIRECTED", direction: OUT) +} +---- + +You would have been able to run the following query: + +[source, graphql, indent=0] +---- +query { + movies(where: { director_IN: [{ name: "A" }, { name: "B" }] }) { + title + } +} +---- + +You can still achieve exactly the same filter with the following: + +[source, graphql, indent=0] +---- +query { + movies(where: { director: { OR: [{ name: "A" }, { name: "B" }]} }) { + title + } +} +---- diff --git a/modules/ROOT/pages/migration/v3-migration.adoc b/modules/ROOT/pages/migration/v3-migration.adoc new file mode 100644 index 00000000..d9657ba9 --- /dev/null +++ b/modules/ROOT/pages/migration/v3-migration.adoc @@ -0,0 +1,253 @@ +[[v3-migration]] += 3.0.0 Migration +:page-aliases: guides/v3-migration/index.adoc + + +This document lists all breaking changes from version 2.x.y to 3.0.0 and how to update. + +== How to upgrade +Simply update `@neo4j/graphql` using npm or your package manager of choice: + +[source, bash, indent=0] +---- +npm update @neo4j/graphql +---- + +== Asynchronous schema generation +Schema generation is now asynchronous. Instead of using the property `schema`, now the method `getSchema` will return the schema +as a `Promise`. This means that creating a server now requires awaiting for that method: + +Instead of +[source, JavaScript, indent=0] +---- +const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); +const server = new ApolloServer({ + schema: neoSchema.schema, +}); +---- + +Now you'll need to do the following: + +[source, JavaScript, indent=0] +---- +const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); +neoSchema.getSchema().then((schema) => { + const server = new ApolloServer({ + schema: schema + }); +}); +---- + +== Relationship changes +This release contains an overhaul of our relationship validations, which will require a few changes to the schema. + +=== Many-to-* relationships +To improve consistency and validation, **all** "many-to-*" relationships need to be defined as _required_ in the schema: + +[source, graphql, indent=0] +---- +type Movie { + actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) + director: Director @relationship(type: "DIRECTED", direction: IN) +} +---- + +Note that any other notation, such as `[Actor]` or `[Actor!]` will **not** be valid. "One-to-one" relationships +such as `Director` remain unchanged. + +=== Relationship cardinality +Runtime checks for "one-to*" relationships have been added in this release, ensuring that the correct number of relationships exist. This means that some +databases with inconsistent relationships between the schema definition and the actual data may now fail in some queries. +This may have happened due to different reasons such as direct changes in the database or changes to the type definitions. +Previous versions of `@neo4j/graphql` did not have any consistency check, so normal use of these versions may have lead to +inconsistent relationships. + +For these cases, please ensure that the database is following your schema definition or update the schema to reflect the +actual existing relationships, taking care of which relationships are 1-to-* or many-to-many. + +== Count query no longer supported +Queries using `count` at the root level are no longer supported. For example: +[source, graphql, indent=0] +---- +query { + usersCount +} +---- + +The same operation, can now be achieved with a xref::queries-aggregations/queries.adoc#_counting_using_aggregation[count aggregation] query: + +[source, graphql, indent=0] +---- +query { + usersAggregate { + count + } +} +---- + +=== Relationship filters +`where` filters for relationship queries now explicitly state `ALL`, `NONE`, `SINGLE`, and `SOME` as part of filter name. + +Queries using old relationship filters, will now need to use `\{relationship\}_SOME`. For example: + +[source, graphql, indent=0] +---- +query { + movies(where: { + actors: { + name: "John" + } + }) { + title + } +} +---- + +Should be: + +[source, graphql, indent=0] +---- +query { + movies(where: { + actors_SOME: { + name: "John" + } + }) { + title + } +} +---- + +And, instead of `_NOT`, `_NONE` should be used. + +NOTE: Old queries will still work in this release, but are marked as `@deprecated` and will not be available in the future. + +== `@ignore` directive renamed to `@computed` +To better reflect its intended usage, the `@ignore` directive is now named `@computed`. Behaviour is unchanged, so you just need to +rename this directive in your schema. + +== Auth plugin system +label:deprecated[] + +Auth setup now relies on _plugins_ to setup the configuration. +You'll need to install `@neo4j/graphql-plugin-auth` or a custom plugin. + +=== JWT auth +For JWT authorization, instead of the previous configuration: +[source, javascript, indent=0] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, + config: { + jwt: { + secret + } + } +}); +---- + +Now the configuration should be passed through `Neo4jGraphQLAuthJWTPlugin`: + +[source, javascript, indent=0] +---- +import { Neo4jGraphQL } from "@neo4j/graphql"; +import { Neo4jGraphQLAuthJWTPlugin } from "@neo4j/graphql-plugin-auth"; + +const neoSchema = new Neo4jGraphQL({ + typeDefs, + plugins: { + auth: new Neo4jGraphQLAuthJWTPlugin({ + secret: "super-secret" + }) + } +}); +---- + + +=== JWKS decoding + +https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets[JSON Web Key Sets] are now supported through `Neo4jGraphQLAuthJWKSPlugin`. + +Instead of setting the endpoint directly: +[source, javascript, indent=0] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, + config: { + jwt: { + jwksEndpoint: "https://YOUR_DOMAIN/.well-known/jwks.json" + } + } +}); +---- + +Now the `Neo4jGraphQLAuthJWKSPlugin` would take care of that: +[source, javascript, indent=0] +---- +import { Neo4jGraphQL } from "@neo4j/graphql"; +import { Neo4jGraphQLAuthJWKSPlugin } from "@neo4j/graphql-plugin-auth"; + +const neoSchema = new Neo4jGraphQL({ + typeDefs, + plugins: { + auth: new Neo4jGraphQLAuthJWKSPlugin({ + jwksEndpoint: "https://YOUR_DOMAIN/well-known/jwks.json", + }) + } +}); +---- + +NOTE: Please, refer to xref::authentication-and-authorization/index.adoc[auth setup] before setting up auth. + +== Types plurals changes +To improve consistency, some automatically generated plurals (e.g. `createActors`) have changed. This may cause issues if +your types use conventions such as `snake_case`. + +Because of this, you may find generated queries and mutations may have different names. If you encounter this problem, +please update your clients to use the new query names or use the `plural` option in the xref::/type-definitions/directives/database-mapping.adoc#type-definitions-node[@node directive] +to force a custom plural value. + +== Custom Directives +Defining and applying custom directives has changed significantly, if you are using or plan to use custom directives, make +sure to check the up-to-date documentation on xref::/type-definitions/directives/custom-directives.adoc[custom directives]. + +== Types changes +Some automatically generated types have changed to improve consistency. +These should not require any changes from most developers, unless types names are directly used. + +Some automatically generated types have changed to improve consistency. +These should not require any changes from the developer in most cases, unless in cases where types names are directly used. + +=== Removal of nested operation fields for `connectOrCreate` +Input types for `onCreate` in `connectOrCreate` operations no longer accept relationship fields. They were originally added in error and did not function as one would expect, so there is no regression in functionality. + +=== Non Nullable Aggregation Results +Aggregation results may now be non-nullable for required fields, yielding more accurate types. + +For example, for the following types: +[source, graphql, indent=0] +---- +type User { + name: String! + lastName: String +} +---- + +Will yield different types for aggregations over `name` and `lastName`: +[source, graphql, indent=0] +---- +type UserAggregateSelection { + count: Int! + name: StringAggregateSelectionNonNullable! + lastName: StringAggregateSelectionNullable! +} +---- + +=== ConnectionWhere types renamed +`ConnectionWhere` types renamed to improve consistency with other similarly named types. + +== Neo4j support +Neo4j 4.1 is no longer supported in 3.0.0, inline with the https://neo4j.com/developer/kb/neo4j-supported-versions/[supported versions list]. + +== GraphQL support +`graphql@^15.0.0` is no longer supported, please upgrade to `graphql@^16.0.0` using `npm` or the package manager of your choice. diff --git a/modules/ROOT/pages/migration/v4-migration/index.adoc b/modules/ROOT/pages/migration/v4-migration/index.adoc new file mode 100644 index 00000000..6375ab27 --- /dev/null +++ b/modules/ROOT/pages/migration/v4-migration/index.adoc @@ -0,0 +1,1015 @@ +[[v4-migration]] += 4.0.0 Migration +:page-aliases: guides/v4-migration/index.adoc + + +This document lists all breaking changes from version 3.x.y to 4.0.0 and how to update. + +== How to upgrade +Simply update `@neo4j/graphql` using npm or your package manager of choice: + +[source, bash, indent=0] +---- +npm update @neo4j/graphql +---- + +== Constructor arguments + +If you were passing any arguments from https://the-guild.dev/graphql/tools/docs/api/interfaces/schema_src.iexecutableschemadefinition[`IExecutableSchemaDefinition`] into the library other than `typeDefs` and `resolvers`, these are no longer supported. + +=== Removal of `config` + +==== `debug` + +The programmatic toggle for debug logging has been moved from `config.enableDebug` to simply `debug`. + +An example of `enableDebug`: + +[source, javascript, indent=0] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const neo4j = require("neo4j-driver"); +const { ApolloServer } = require("apollo-server"); + +const typeDefs = ` + type Movie { + title: String! + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const neoSchema = new Neo4jGraphQL({ + typeDefs, + driver, + config: { + enableDebug: true, + } +}); +---- + +This now becomes: + +[source, javascript, indent=0] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const neo4j = require("neo4j-driver"); +const { ApolloServer } = require("apollo-server"); + +const typeDefs = ` + type Movie { + title: String! + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const neoSchema = new Neo4jGraphQL({ + typeDefs, + driver, + debug: true, +}); +---- + +==== `driverConfig` moved to context + +Session configuration is now available only in the context under the `sessionConfig` key. + +This was previously `driverConfig`, available in both the constructor and in the context: + +[source, javascript, indent=0] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, + config: { + driverConfig: { + database: "different-db" + }, + }, +}) +---- + +The new `sessionConfig` key is only available in the context: + +[source, javascript, indent=0] +---- +import { ApolloServer } from '@apollo/server'; +import { startStandaloneServer } from '@apollo/server/standalone'; +import { Neo4jGraphQL } from "@neo4j/graphql"; +import neo4j from "neo4j-driver"; + +const typeDefs = `#graphql + type User { + name: String + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); + +const server = new ApolloServer({ + schema: await neoSchema.getSchema(), +}); + +await startStandaloneServer(server, { + context: async ({ req }) => ({ sessionConfig: { database: "my-database" }}), +}); +---- + +The `bookmarks` key has been removed because it is no longer needed with the bookmark manager of the newer driver. + +==== `enableRegex` replaced by `MATCHES` in features.filters + +`config.enableRegex` has been replaced by `MATCHES` in features.filters. With this change comes more granularity in the feature configuration. You can now enable the `MATCHES` filter on `String` and `ID` fields separately. + +A direct replacement of the `enableRegex: true` configuration would be as follows: + +[source, javascript, indent=0] +---- +neoSchema = new Neo4jGraphQL({ + typeDefs, + features: { + filters: { + String: { + MATCHES: true, + }, + ID: { + MATCHES: true, + }, + }, + }, +}); +---- + +==== `queryOptions` moved to the context + +If you had a need to pass in Cypher query options for query tuning, this interface has been changed. + +The config option `queryOptions` has now become `cypherQueryOptions` inside the context function, and it now accepts simple strings instead of enums. + +The following is an example before the change: + +[source, javascript, indent=0] +---- +const { Neo4jGraphQL, CypherRuntime } = require("@neo4j/graphql"); +const { ApolloServer } = require("apollo-server"); + +const typeDefs = ` + type Movie { + title: String! + } +`; + +const neoSchema = new Neo4jGraphQL({ + typeDefs, + config: { + queryOptions: { + runtime: CypherRuntime.INTERPRETED, + }, + }, +}); +---- + +This is what is required after the change: + +[source, javascript, indent=0] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const { ApolloServer } = require("apollo-server"); + +const typeDefs = ` + type Movie { + title: String! + } +`; + +const neoSchema = new Neo4jGraphQL({ + typeDefs, +}); + +const server = new ApolloServer({ + schema: await neoSchema.getSchema(), +}); + +await startStandaloneServer(server, { + context: async ({ req }) => ({ cypherQueryOptions: { runtime: "interpreted" }}), +}); +---- + +This reflects the fact that the Cypher query options are set on a per-request basis. + +[[startup-validation]] +==== `skipValidateTypeDefs` + +The argument `skipValidateTypeDefs` has been moved to the top-level of the constructor input and renamed `validate`, which defaults to `true`. + +To disable type definition validation, the following config option should be used: + +[source, javascript, indent=0] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, + validate: false, +}) +---- + +If you started using the `config.startupValidation` option, this has also been rolled into the same `validate` setting for simplicity. +The `resolvers` option of this is now just a warning, and `noDuplicateRelationshipFields` is now a mandatory check rolled into `validate`. + +[subscriptions-options] +=== Subscription options + +Subscriptions are no longer configured as a plugin, but as a feature within the `features` option. + +This means that, instead of: + +[source, javascript] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, + plugins: { + subscriptions: plugin, + }, +}); +---- + +Subscriptions are now defined as: + +[source, javascript] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, + features: { + subscriptions: plugin, + }, +}); +---- + +==== Default subscriptions + +The class `Neo4jGraphQLSubscriptionsSingleInstancePlugin` is no longer exported. +Instead, the default subscriptions behavior can be enabled by setting the `subscriptions` option to `true` . + +Instead of: + +[source, javascript, indent=0] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, + plugin: { + subscriptions: new Neo4jGraphQLSubscriptionsSingleInstancePlugin(), + }, +}); +---- + +The default subscriptions can be enabled with: + +[source, javascript, indent=0] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, + features: { + subscriptions: true + }, +}); +---- + +==== Neo4j GraphQL Subscriptions AMQP package + +The name of the interface underlying the Subscriptions system has changed from `Neo4jGraphQLSubscriptionsPlugin` to `Neo4jGraphQLSubscriptionsEngine`. +If you were previously using the `@neo4j/graphql-plugins-subscriptions-amqp` package, this has been changed to `@neo4j/graphql-amqp-subscriptions-engine` to reflect this underlying change. + +Please uninstall the previous package and install the new one: + +[source, bash, indent=0] +---- +npm uninstall @neo4j/graphql-plugins-subscriptions-amqp +npm install @neo4j/graphql-amqp-subscriptions-engine +---- + +Please then update any imports from: + +[source, javascript, indent=0] +---- +import { Neo4jGraphQLSubscriptionsAMQPPlugin } from "@neo4j/graphql-plugins-subscriptions-amqp"; +---- + +To: + +[source, javascript, indent=0] +---- +import { Neo4jGraphQLAMQPSubscriptionsEngine } from "@neo4j/graphql-amqp-subscriptions-engine"; +---- + +Then change the instantiations from: + +[source, javascript, indent=0] +---- +const plugin = new Neo4jGraphQLSubscriptionsAMQPPlugin({ + connection: { + hostname: "localhost", + username: "guest", + password: "guest", + }, +}); +---- + +To: + +[source, javascript, indent=0] +---- +const subscriptionsEngine = new Neo4jGraphQLAMQPSubscriptionsEngine({ + connection: { + hostname: "localhost", + username: "guest", + password: "guest", + }, +}); +---- + +==== Custom Subscription Plugins + +The underlying subscription system has not changed. +Custom behavior can be implemented the same way, by creating a class implementing the interface described in xref::subscriptions/engines.adoc#custom-subscription[Subscriptions Engines]. + +However, if using TypeScript, the exported interface to implement these classes has been renamed from `Neo4jGraphQLSubscriptionsPlugin` to `Neo4jGraphQLSubscriptionsEngine`. + +== Updated Directives + +We have renamed a number of directives and their arguments, in order to make using `@neo4j/graphql` more intuitive. + +=== `@alias` values are now automatically escaped + +Properties in the alias directive automatically escaped using backticks. If you were using backticks in the `property` argument of your `@alias` directives, you should now remove the escape strings as this is covered by the library. + +[source, graphql, indent=0] +---- +type User { + id: ID! @id + username: String! @alias(property: "dbUserName") +} +---- + +[populatedBy-migration] +=== `@callback` renamed to `@populatedBy` + +Previously, there was ambiguity over the behaviour of `@callback`. As the directive is used to populate a value on input, it has been renamed `@populatedBy` to reflect this. +Additionally, the `name` argument was previously used to specify the callback used to populate the field's value. +This has been renamed to `callback` to make it clear that it refers to a callback. + +Therefore, the following usage of the directive would be invalid: + +[source, graphql, indent=0] +---- +type User { + id: ID! @callback(name: "nanoid", operations: [CREATE]) + firstName: String! + surname: String! +} +---- + +It would instead need to be updated to use the new directive and argument as below: + +[source, graphql, indent=0] +---- +type User { + id: ID! @populatedBy(callback: "nanoid", operations: [CREATE]) + firstName: String! + surname: String! +} +---- + +Configuration for callbacks has also been moved as part of this change. Before these changes, a callback named `nanoid` would need to be defined as below: + +[source, javascript, indent=0] +---- +new Neo4jGraphQL({ + typeDefs, + config: { + callbacks: { + nanoid: () => { return nanoid(); } + } + } +}); +---- + +This has been changed to use the `features` constructor object: + +[source, javascript, indent=0] +---- +new Neo4jGraphQL({ + typeDefs, + features: { + populatedBy: { + callbacks: { + nanoid: () => { return nanoid(); } + } + } + } +}); +---- + +[customResolver-migration] +=== `@computed` renamed to `@customResolver` + +Previously, there was ambiguity over the behaviour of `@computed` and it wasn't clear that it was intended to be used with a custom resolver. In order to make this clear, `@computed` has been renamed to `@customResolver`. +Furthermore, the behaviour of the `from` argument was not clear. The argument is used to specify which fields other fields are required by the custom resolver. As a result, `from` has been renamed to `requires`. + +These changes mean that the following type definition is invalid in version 4.0.0: + +[source, graphql, indent=0] +---- +type User { + firstName: String! + lastName: String! + fullName: String! @computed(from: ["firstName", "lastName"]) +} +---- + +Instead, it would need to be updated to use the new directive and argument as below: + +[source, graphql, indent=0] +---- +type User { + firstName: String! + lastName: String! + fullName: String! @customResolver(requires: ["firstName", "lastName"]) +} +---- + +Note that before and after these changes, a custom resolver would need to be defined as below: + +[source, javascript, indent=0] +---- +new Neo4jGraphQL({ + typeDefs, + resolvers: { + User: { + fullName: ({ firstName, lastName }, args, context, info) => (`${firstName} ${lastName}`), + } + } +}); +---- + +==== `requires` changes + +In version 4.0.0, it is now possible to require non-scalar fields. This means it is also possible to require fields on related type. +To make this possible, the `requires` argument now accept a graphql selection set instead of a list of strings. + +Therefore, the following type definitions: + +[source, graphql, indent=0] +---- +type User { + firstName: String! + lastName: String! + fullName: String! @customResolver(requires: ["firstName", "lastName"]) +} +---- + +Would need to be modified to use a selection set as below: + +[source, graphql, indent=0] +---- +type User { + firstName: String! + lastName: String! + fullName: String! @customResolver(requires: "firstName lastName") +} +---- + +Below is a more advanced example showing what the selection set is capable of: + +[source, graphql, indent=0] +---- +interface Publication { + publicationYear: Int! +} + +type Author { + name: String! + publications: [Publication!]! @relationship(type: "WROTE", direction: OUT) + publicationsWithAuthor: [String!]! + @customResolver( + requires: "name publications { publicationYear ...on Book { title } ... on Journal { subject } }" + ) +} + +type Book implements Publication { + title: String! + publicationYear: Int! + author: [Author!]! @relationship(type: "WROTE", direction: IN) +} + +type Journal implements Publication { + subject: String! + publicationYear: Int! + author: [Author!]! @relationship(type: "WROTE", direction: IN) +} +---- + +Additionally, the requires argument also validates the required selection set against your type definitions. +Therefore, as there is no field called `someFieldThatDoesNotExist`, an error would be thrown on startup if you tried to use the following type definitions: + +[source, graphql, indent=0] +---- +type User { + firstName: String! + lastName: String! + fullName: String! @customResolver(requires: "firstName someFieldThatDoesNotExist") +} +---- + +=== `@cypher` changes +The default behaviour of the `@cypher` directive regarding the translation will change: Instead of using https://neo4j.com/labs/apoc/4.0/overview/apoc.cypher/apoc.cypher.runFirstColumnMany/[apoc.cypher.runFirstColumnMany] it will directly wrap the query within a `CALL { }` subquery. This behvaiour has proven to be much more performant for the same queries, however, it may lead to unexpected changes, mainly when using Neo4j 5.x, where the subqueries need to be _aliased_. + +On top of that, to improve performance, it is recommended to pass the returned alias in the property `columnName`, to ensure the subquery is properly integrated into the larger query. + +For example: + +The graphql query: +[source, graphql, indent=0] +---- +type query { + test: String! @cypher(statement: "RETURN 'hello'") +} +---- + +Would get translated to: +[source,cypher, indent=0] +---- +CALL { + RETURN 'hello' +} +WITH 'hello' AS this +RETURN this +---- + +Which is invalid in Neo4j 5.x. + +To fix it we just need to ensure the `RETURN` elements are aliased: +[source, graphql, indent=0] +---- +type query { + test: String! @cypher(statement: "RETURN 'hello' as result") +} +---- + +This will be a breaking change, but this new behaviour can be used, as an experimental option with the `columnName` flag in the `@cypher` directive: + +[source, graphql, indent=0] +---- +type query { + test: String! @cypher(statement: "RETURN 'hello' as result", columnName: "result") +} +---- + +Additionally, escaping strings is no longer needed. + +=== `@exclude` removed + +The `@exclude` directive has been removed in favor of much more granular configuration directives. + +The new `@query`, `@mutation` and `@subscription` directives instead allow for fully granular configuration for each operation. + +As a direct migration, the following usages are equivalent: + +* `@exclude` and `@query(read: false, aggregate: false) @mutation(operations: []) @subscription(events: [])`. +* `@exclude(operations: [READ])` and `@query(read: false, aggregate: false)`. +* `@exclude(operation: [CREATE, UPDATE, DELETE])` and `@mutation(operations: [])`. + +Whilst there is more verbosity, the directives are significantly more powerful and extensible as the library gains features. + +[full-text-migration] +=== `@fulltext` changes + +In version 4.0.0, a number of improvements have been made to full-text queries. These include the ability to return the full-text score, filter by the score and sorting by the score. + +However, these improvements required a number of breaking changes. + +==== Query changes + +Full-text queries now need to be performed using a top-level query, instead of being performed using an argument on a node query. + +As a result, the following query is now invalid: + +[source, graphql, indent=0] +---- +query { + movies(fulltext: { movieTitleIndex: { phrase: "Some Title" } }) { + title + } +} +---- + +The new top-level queries can be used to return the full-text score, which indicates the confidence of a match, as well as the nodes that have been matched. + +.The new top-level queries accept the following arguments: +* `phrase` which specifies the string to search for in the full-text index. +* `where` which accepts a min/max score as well as the normal filters available on a node. +* `sort` which can be used to sort using the score and node attributes. +* `limit` which is used to limit the number of results to the given integer. +* `offset` which is used to offset by the given number of results. + +The new top-level queries means that for the following type definition: + +[source, graphql, indent=0] +---- +type Movie @fulltext(indexes: [{ indexName: "MovieTitle", fields: ["title"] }]) { # Note that indexName is the new name for the name argument. More about this below. + title: String! +} +---- + +The following top-level query and type definitions would be generated by the library: + +[source, graphql, indent=0] +---- +type Query { + movieFulltextMovieTitle(phrase: String!, where: MovieFulltextWhere, sort: [MovieFulltextSort!], limit: Int, offset: Int): [MovieFulltextResult!]! +} + +"""The result of a fulltext search on an index of Movie""" +type MovieFulltextResult { + score: Float + movies: Movie +} + +"""The input for filtering a fulltext query on an index of Movie""" +input MovieFulltextWhere { + score: FloatWhere + movie: MovieWhere +} + +"""The input for sorting a fulltext query on an index of Movie""" +input MovieFulltextSort { + score: SortDirection + movie: MovieSort +} + +"""The input for filtering the score of a fulltext search""" +input FloatWhere { + min: Float + max: Float +} +---- + +This query can be used to perform a full-text query as below: + +[source, graphql, indent=0] +---- +query { + movieFulltextMovieTitle( + phrase: "Full Metal Jacket", + where: { score: min: 0.4 }, + sort: [{ movie: { title: ASC } }], + limit: 5, + offset: 10 + ) { + score + movies { + title + } + } +} +---- + +The above query would be expected to return results in the following format: + +[source, json, indent=0] +---- +{ + "data": { + "movieFulltextMovieTitle": [ + { + "score": 0.44524085521698, + "movie": { + "title": "Full Moon High" + } + }, + { + "score": 1.411118507385254, + "movie": { + "title": "Full Metal Jacket" + } + } + ] + } +} +---- + +==== Argument changes + +.The following changes have been made to `@fulltext` arguments: +* `queryName` has been added to specify a custom name for the top-level query that is generated. +* `name` has been renamed to `indexName` to avoid ambiguity with the new `queryName` argument. + +These changes means that the following type definition is now invalid: + +[source, graphql, indent=0] +---- +type Movie @fulltext(indexes: [{ name: "MovieTitle", fields: ["title"] }]) { + title: String! +} +---- + +The `name` argument would need to be replaced with `indexName` as below: + +[source, graphql, indent=0] +---- +type Movie @fulltext(indexes: [{ indexName: "MovieTitle", fields: ["title"] }]) { + title: String! +} +---- + +The `queryName` argument can be used as below: + +[source, graphql, indent=0] +---- +type Movie @fulltext(indexes: [{ queryName: "moviesByTitle", indexName: "MovieTitle", fields: ["title"] }]) { + title: String! +} +---- + +This means the top-level query would now be `moviesByTitle` instead of `movieFulltextMovieTitle`: + +[source, graphql, indent=0] +---- +type Query { + moviesByTitle(phrase: String!, where: MovieFulltextWhere, sort: [MovieFulltextSort!], limit: Int, offset: Int): [MovieFulltextResult!]! +} +---- + +=== `@id` changes + +The `@id` directive has been completely pared back in version 4.0.0, with _all_ of its arguments removed. +This has been done to reduce the number of features that this directive was used to toggle, and to ensure that its behaviour is consistent no matter where it is used. + +==== `autogenerate` + +The default value of `autogenerate` was `true`. If this was set to `false`, the `@id` directive was almost a no-op only used to manage a unique node property constraint. Use the `@unique` directive instead. + +==== `global` + +The `global` argument was used to configure the field that would form the global node identifier for Relay. + +This functionality has been moved into its own directive, `@relayId`. The use of `@relayId` will ensure a unique node property constraint for the field. + +==== `unique` + +The `@id` directive used to also manage unique node property constraints for a field. This functionality has now been removed, use the `@unique` directive in combination with `@id` if you want the field to be backed by a constraint. + +=== `@node` changes + +[plural-migration] +==== `plural` argument removed from `@node` and replaced with `@plural` + +How a type name is pluralised has nothing to do with nodes in the database. As a result, having a `plural` argument on the `@node` directive did not make sense. +As a result, the `plural` argument of `@node` has been removed and replaced with a new `@plural` directive. The `@plural` directive takes the pluralised type name using the `value` argument. + +This means that the following type definition is invalid: + +[source, graphql, indent=0] +---- +type Tech @node(label: "TechDB", plural: "Techs") { + name: String +} +---- + +It would need to be updated to use the new directive as below: + +[source, graphql, indent=0] +---- +type Tech @node(label: "TechDB") @plural(value: "Techs") { + name: String +} +---- + +[label-migration] +==== `label` and `additionalLabels` arguments removed from `@node` and replaced with new argument `labels` + +There is no concept of a "main label" in the Neo4j database. As such, keeping these two separate arguments causes a disconnect between the database and the GraphQL library. +As a result, the `label` and `additionalLabels` arguments have been condensed into a single argument `labels` which will accept a list of string labels that used when a node of the given GraphQL type is created. +Please note that defining `labels` means you take control of the database labels of the node. Indexes and constraints in Neo4j only support a single label, for which the first element of the `labels` argument will be used. + +The equivalent of using just the `label` argument is now a list with a single value: + +[source, graphql, indent=0] +---- +type Tech @node(label: "TechDB") { + name: String +} +# becomes +type Tech @node(labels: ["TechDB"]) { + name: String +} +---- + +When creating the equivalent of using just the `additionalLabels` argument now requires the first value in the list to be the GraphQL type name: + +[source, graphql, indent=0] +---- +type Tech @node(additionalLabels: ["TechDB"]) { + name: String +} +# becomes +type Tech @node(labels: ["Tech", "TechDB"]) { + name: String +} +---- + +The equivalent of using both deprecated arguments is a list with all the values concatenated: + +[source, graphql, indent=0] +---- +type Tech @node(label: "TechDB", additionalLabels: ["AwesomeTech"]) { + name: String +} +# becomes +type Tech @node(labels: ["TechDB", "AwesomeTech"]) { + name: String +} +---- + +As before, providing none of these arguments results in the node label being the same as the GraphQL type name. + +Please note the implications on constraints. +In the following example, a unique constraint will be asserted for the label `Tech` and the property `name`: + +[source, graphql, indent=0] +---- +type Tech @node(labels: ["Tech", "TechDB"]) { + name: String @unique +} +---- + +=== `@queryOptions` removed and `limit` argument moved to `@limit` + +If you were using the `@queryOptions` directive to configure the default and max values for limiting the data returned by queries, for instance: + +[source, graphql, indent=0] +---- +type Record @queryOptions(limit: { default: 10, max: 100 }) { + id: ID! +} +---- + +This is now achieved by using the `@limit` directive: + +[source, graphql, indent=0] +---- +type Record @limit(default: 10, max: 100) { + id: ID! +} +---- + +=== `@readonly` and `@writeonly` removed + +The `@readonly` and `@writeonly` directives have been removed in favor of more granular configuration directives. +The new `@selectable` and `@settable` directives can be used to configure not only if fields are readable or writable, but also when they should be readable or writable. + +As a direct migration, the following usages are equivalent: + +* `@readonly` and `@settable(onCreate: false, onUpdate: false)`. +* `@writeonly` and `@selectable(onRead: false, onAggregate: false)`. + +[relationship-aggregate] +=== `@relationship` changes + +==== Relationship types are now automatically escaped + +Relationship types are now automatically escaped. If you have previously escaped your relationship types using backticks, you must now remove these as this is covered by the library. + +==== `aggregate` argument + +In version 4.0.0, the default value of the aggregate argument will be false. +This means that aggregation operation fields will no longer be generated by default when a relationship is defined using the `@relationship` directive. + +For instance, given the following type definitions: + +[source, graphql, indent=0] +---- +type Movie { + title: String! +} + +type Actor { + name: String! + actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) +} +---- + +These will no longer generate `actedInAggregate` for the type `Actor`. + +To enable it, explicitly set the aggregate argument as `true`: + +[source, graphql, indent=0] +---- +type Movie { + title: String! +} + +type Actor { + name: String! + actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, aggregate: true) +} +---- + +=== `@relationshipProperties` now mandatory + +Upcoming changes to interfaces require us to distinguish between interfaces that are used to specify relationship properties, and others. Therefore, the `@relationshipProperties` directive is now required on all relationship property interfaces. +If it is not included, an error will be thrown. + +As a result, in version 4.0.0, the following type definitions are invalid: + +[source, graphql, indent=0] +---- +type Person { + name: String! + movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedIn") +} + +type Movie { + title: String! + actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") +} + +interface ActedIn { + screenTime: Int! +} +---- + +`ActedIn` must be decorated with `@relationshipProperties`: + +[source, graphql, indent=0] +---- +interface ActedIn @relationshipProperties { + screenTime: Int! +} +---- + +== Miscellaneous changes + +=== Duplicate relationship fields are now checked for + +It was possible to define schemas with types that have multiple relationship fields connected by the same type of relationships. Instances of this scenario are now detected during schema generation and an error is thrown so developers are informed to remedy the type definitions. + +An example of what is now considered invalid with these checks: + +[source, graphql, indent=0] +---- +type Team { + player1: Person! @relationship(type: "PLAYS_IN", direction: IN) + player2: Person! @relationship(type: "PLAYS_IN", direction: IN) + backupPlayers: [Person!]! @relationship(type: "PLAYS_IN", direction: IN) +} + +type Person { + teams: [Team!]! @relationship(type: "PLAYS_IN", direction: OUT) +} +---- + +In this example, there are multiple fields in the `Team` type which have the same `Person` type, the same `@relationship` type and ("PLAYS_IN") direction (IN). This is an issue when returning data from the database, as there would be no difference between `player1`, `player2` and `backupPlayers`. Selecting these fields would then return the same data. + +These checks can be disabled by disabling all validation in the library, however, this is not recommended unless in production with 100% confidence of type definitions input. + +[source, javascript, indent=0] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, + validate: false, +}); +---- + +[[opt-in-aggregation]] +=== Opt-in Aggregation + +Aggregation operations are no longer generated by default. +They can be enabled case by case using the directives xref::/schema-configuration/type-configuration.adoc#_query[`@query`] and xref::/schema-configuration/field-configuration.adoc#_relationship[`@relationship`]. + +You can enable the operation fields `actorsAggregate` and `actedInAggregate` like this: + +[source, graphql, indent=0] +---- +type Movie { + title: String! +} + +type Actor @query(aggregate: true) { + name: String! + actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, aggregate: true) +} +---- + +=== `cypherParams` + +`cypherParams` is available in the context to provide the ability to pass arbitrary parameters to a custom Cypher query. This functionality remains in 4.0.0, but you no longer have to use the `$cypherParams` prefix to reference these parameters. From 7039c8e02edf0766224d4415cfb362fb59c6df7b Mon Sep 17 00:00:00 2001 From: Lidia Zuin <102308961+lidiazuin@users.noreply.github.com> Date: Tue, 19 Sep 2023 12:06:47 +0200 Subject: [PATCH 11/28] Update docs-teardown.yml --- .github/workflows/docs-teardown.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/docs-teardown.yml b/.github/workflows/docs-teardown.yml index a4cdd4bf..6cd1c5fa 100644 --- a/.github/workflows/docs-teardown.yml +++ b/.github/workflows/docs-teardown.yml @@ -4,8 +4,7 @@ name: "Documentation Teardown" on: pull_request_target: branches: - - "main" - - "dev" + - "4.x" types: - closed From 5bd650d3802d451935f42db1807eb7bc1ba1f49d Mon Sep 17 00:00:00 2001 From: Lidia Zuin <102308961+lidiazuin@users.noreply.github.com> Date: Tue, 19 Sep 2023 12:07:11 +0200 Subject: [PATCH 12/28] Update docs-pr.yml --- .github/workflows/docs-pr.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yml index 02bdd80d..d5ecf362 100644 --- a/.github/workflows/docs-pr.yml +++ b/.github/workflows/docs-pr.yml @@ -7,8 +7,7 @@ name: "Verify PR" on: pull_request: branches: - - "main" - - "dev" + - "4.x" jobs: From dc07ba93cd5ec7a41946b3f7d77d29a384d811e1 Mon Sep 17 00:00:00 2001 From: Lidia Zuin <102308961+lidiazuin@users.noreply.github.com> Date: Tue, 19 Sep 2023 12:27:14 +0200 Subject: [PATCH 13/28] Editorial review of the latest additions (#34) * Update publish.yml * Editorial review of the most recent changes * reverting changes to partials * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * Add page-aliases for version 4 (#31) * revert * Apply suggestions from code review Co-authored-by: Michael Webb <28074382+mjfwebb@users.noreply.github.com> --------- Co-authored-by: Neil Dewhurst Co-authored-by: Michael Webb <28074382+mjfwebb@users.noreply.github.com> --- .../authorization.adoc | 40 +++++----- .../impersonation-and-user-switching.adoc | 35 ++++---- .../index.adoc | 6 +- .../pages/integrations/apollo-federation.adoc | 79 ++++++++++--------- modules/ROOT/pages/subscriptions/events.adoc | 33 ++++++-- 5 files changed, 115 insertions(+), 78 deletions(-) diff --git a/modules/ROOT/pages/authentication-and-authorization/authorization.adoc b/modules/ROOT/pages/authentication-and-authorization/authorization.adoc index 21b4757a..4fab80aa 100644 --- a/modules/ROOT/pages/authentication-and-authorization/authorization.adoc +++ b/modules/ROOT/pages/authentication-and-authorization/authorization.adoc @@ -1,25 +1,25 @@ +[[authorization]] +:description: This page describes how to set up authorization features in the Neo4j GraphQL Library. = Authorization -Authorization rules concern themselves with specific data which is executed against during a generated Cypher query. -They are evaluated via predicates in the Cypher generated from a GraphQL query, so they execute within the context of nodes and their properties. +Authorization rules cover what specific data a generated Cypher query is executed against. +They use predicates to evaluate the Cypher generated from a GraphQL query, thus allowing or disallowing execution within the context of nodes and their properties. -All authorization rules have an implied requirement for authentication, given that the rules are normally evaluated -against values in the JWT payload. +All authorization rules have an implied requirement for authentication, given that the rules are normally evaluated against values in the JWT payload. -In the case of explicit authentication, configured using the `@authentication` directive, is only ever evaluated -during Cypher translation time, and unauthenticated requests with queries requiring authentication -will never reach the database. +In the case of explicit authentication, configured using the `@authentication` directive, it is only ever evaluated during Cypher translation time. +Unauthenticated requests with queries requiring authentication never reach the database. == Rules -=== Filter +=== Filtering -Filter rules filter out data which users do not have access to, without throwing any errors. These rules -are evaluated in the database, with the rules translated into filter predicates and evaluated against matched data. -Filter rules are valuable because they protect your data as well as obfuscate the information on the _existence_ of -data to unauthorized users. +Filtering rules filter out data which users do not have access to, without throwing any errors. +These rules are evaluated in the database, then translated into filtering predicates that are evaluated against matched data. -For instance, to filter out `Post` nodes which don't belong to the current `User`: +Filtering rules protect data as well as obfuscate the information on the _existence_ of that data to unauthorized users. + +For instance, here is how to filter out `Post` nodes which don't belong to the current `User`: [source, graphql, indent=0] ---- @@ -36,13 +36,13 @@ type Post @authorization(filter: [ } ---- -=== Validate +=== Validating -Validate rules throw an error if a query executes against data which users do not have access to. These rules are -evaluated in the database via filtering predicates containing calls to +Validating rules throw an error if a query is executed against data which users do not have access to. +These rules are evaluated in the database via filtering predicates containing calls to https://neo4j.com/docs/apoc/current/overview/apoc.util/apoc.util.validatePredicate/[`apoc.util.validatePredicate`]. -For instance, to throw an error if a `User` is accessed by anyone but the user themselves or an admin: +For instance, here is how to throw an error if a `User` is accessed by anyone but the user themselves or an admin: [source, graphql, indent=0] ---- @@ -58,12 +58,12 @@ type User @authorization(validate: [ } ---- -== Authorization not requiring authentication +== Authorization without authentication Authentication is implicitly required for every authorization check by default, but this can be disabled on a per-rule basis. -This might be desired perhaps if a node has a property which flags whether the node should be public or not. +This could be the case, for instance, when a node has a property which flags whether the node should be public or not. -For instance, `Post` nodes might be private and belong to a particular `User`, or be public and readable by any user: +For instance, in the case where some `Post` nodes are private and belong to a particular `User`, while other `Post` nodes are public and readable by any user, here is how to set this up: [source, graphql, indent=0] ---- diff --git a/modules/ROOT/pages/authentication-and-authorization/impersonation-and-user-switching.adoc b/modules/ROOT/pages/authentication-and-authorization/impersonation-and-user-switching.adoc index 5ec4da36..dd4c3cb6 100644 --- a/modules/ROOT/pages/authentication-and-authorization/impersonation-and-user-switching.adoc +++ b/modules/ROOT/pages/authentication-and-authorization/impersonation-and-user-switching.adoc @@ -1,3 +1,5 @@ +[[impersonation-and-user-switching]] +:description: This page describes the impersonation and user switching features of the Neo4j GraphQL Library. = Impersonation and user switching Impersonation and user switching are features of the Neo4j database and driver which allow for query execution in a different context to the initial connection. @@ -5,13 +7,16 @@ Impersonation and user switching are features of the Neo4j database and driver w == Impersonation Impersonation still authenticates with the database as the original configured user, but runs the query in the context of an impersonated user. -When impersonating a user, the query is run within the complete security context of the impersonated user and not the authenticated user (home database, permissions, etc.). +When impersonating a user, the query is run within the complete security context of the impersonated user and not the authenticated user (home database, permissions etc). -An example of how to impersonate a different user per request can be found below. In this example, the user to impersonate is taken from a HTTP header `User`: +Consider the following an example of how to impersonate a different user per request. +Here the user to impersonate is taken from a HTTP header `User`: -.TypeScript -[%collapsible] +[.tabbed-example] ==== + +[.include-with-Typescript] +===== [source, typescript, indent=0] ---- import { ApolloServer } from "@apollo/server"; @@ -52,11 +57,10 @@ const { url } = await startStandaloneServer(server, { console.log(`šŸš€ Server ready at: ${url}`); ---- -==== +===== -.JavaScript -[%collapsible] -==== +[.include-with-JavaScript] +===== [source, javascript, indent=0] ---- import { ApolloServer } from "@apollo/server"; @@ -97,6 +101,7 @@ const { url } = await startStandaloneServer(server, { console.log(`šŸš€ Server ready at: ${url}`); ---- +===== ==== == User switching @@ -105,9 +110,11 @@ User switching completely switches the user authenticating with the database for An example of configuring user switching on a per request basis can be found in the example below. Note that the username and password are provided in HTTP headers `User` and `Password`, but this would not be recommended for production use: -.TypeScript -[%collapsible] +[.tabbed-example] ==== + +[.include-with-TypeScript] +===== [source, typescript, indent=0] ---- import { ApolloServer } from "@apollo/server"; @@ -148,11 +155,10 @@ const { url } = await startStandaloneServer(server, { console.log(`šŸš€ Server ready at: ${url}`); ---- -==== +===== -.JavaScript -[%collapsible] -==== +[.include-with-JavaScript] +===== [source, javascript, indent=0] ---- import { ApolloServer } from "@apollo/server"; @@ -193,6 +199,7 @@ const { url } = await startStandaloneServer(server, { console.log(`šŸš€ Server ready at: ${url}`); ---- +===== ==== diff --git a/modules/ROOT/pages/authentication-and-authorization/index.adoc b/modules/ROOT/pages/authentication-and-authorization/index.adoc index 771643e8..f51c7a3e 100644 --- a/modules/ROOT/pages/authentication-and-authorization/index.adoc +++ b/modules/ROOT/pages/authentication-and-authorization/index.adoc @@ -1,15 +1,15 @@ = Authentication and Authorization +:description: This section covers authentication andd authorization features in the Neo4j GraphQL Library. :page-aliases: auth/index.adoc, auth/setup.adoc, auth/authentication.adoc, \ auth/authorization.adoc, auth/auth-directive.adoc, auth/subscriptions.adoc, \ auth/authorization/allow.adoc, auth/authorization/bind.adoc, auth/authorization/roles.adoc, \ auth/authorization/where.adoc, guides/v4-migration/authorization.adoc - [WARNING] -==== +=== The `@auth` directive has been replaced by `@authentication` and `@authorization`. See the xref::migration/v4-migration/authorization.adoc[Migration guide] for details on how to upgrade. -==== +=== * xref::authentication-and-authorization/authentication.adoc[Authentication] - Explicit authentication, configured using the `@authentication` directive. * xref::authentication-and-authorization/authorization.adoc[Authorization] - Authorization rules set using the `@authorization` directive. diff --git a/modules/ROOT/pages/integrations/apollo-federation.adoc b/modules/ROOT/pages/integrations/apollo-federation.adoc index e354b625..0882c0fc 100644 --- a/modules/ROOT/pages/integrations/apollo-federation.adoc +++ b/modules/ROOT/pages/integrations/apollo-federation.adoc @@ -44,38 +44,39 @@ const { url } = await startStandaloneServer(server); console.log(`šŸš€ Server ready at ${url}`); ---- -== Create a new project and install the Apollo Server +== Setup -*Create the project directory* - -Create a directory for a new project and `cd` into it: +To proceed with the convertion, follow these steps: +. Create a directory for a new project and `cd` into it: ++ [source, bash] ---- mkdir neo4j-graphql-subgraph-example cd neo4j-graphql-subgraph-example ---- -*Initialize the project* - -Initialize a new Node.js project with `npm`: - +. Initialize a new Node.js project with `npm`: ++ [source, bash] ---- npm init --yes npm pkg set type="module" ---- ++ +[NOTE] +=== +This how-to guide sets up the project using ES Modules, which allows the usage of features such as top-level `await`. +=== -NOTE: This how-to guide sets up the project using ES Modules, which allows the usage of features such as top-level `await`. - -*Language specific setup* - -Follow the instructions below to set up with either TypeScript or JavaScript: - -.TypeScript -[%collapsible] +. Choose TypeScript or JavaScript to proceed with the setup: ++ +[.tabbed-example] ==== -. Create a `src` directory with an empty `index.ts` file to contain the entrypoint to your code for this project: + +[.include-with-Typescript] +===== +. Create a `src` directory with an empty `index.ts` file to contain the entrypoint to your code: + [source, bash] ---- @@ -90,14 +91,14 @@ touch src/index.ts npm install --save-dev typescript @types/node @tsconfig/node-lts ---- + -. Create an empty `tsconfig.json` file which will contain the compiler configuration for TypeScript: +. Create an empty `tsconfig.json` file containing the compiler configuration for TypeScript: + [source, bash] ---- touch tsconfig.json ---- + -. Add the following configuration to the `tsconfig.json` file created above: +. Add the following configuration to the `tsconfig.json` file: + [source, json] ---- @@ -110,9 +111,13 @@ touch tsconfig.json } ---- + -NOTE: The configuration above extends the https://github.com/tsconfig/bases#node-lts-tsconfigjson[community base for Node.js LTS], provided by the `@tsconfig/node-lts` package installed above. For more information on the available options, see the https://www.typescriptlang.org/tsconfig[TypeScript Compiler docs]. +[NOTE] +=== +This configuration extends the https://github.com/tsconfig/bases#node-lts-tsconfigjson[community base for Node.js LTS], provided by the `@tsconfig/node-lts` package installed above. +For more information on the available options, see the https://www.typescriptlang.org/tsconfig[TypeScript Compiler docs]. +=== + -. Replace the default `scripts` entry in your `package.json` file with the following `scripts` entry: +. Replace the default `scripts` entry in your `package.json` file with the following: + [source, json] ---- @@ -125,12 +130,11 @@ NOTE: The configuration above extends the https://github.com/tsconfig/bases#node // other dependencies } ---- -==== +===== -.JavaScript -[%collapsible] -==== -. Create a `src` directory with an empty `index.js` file to contain the entrypoint to your code for this project: +[.include-with-JavaScript] +===== +. Create a `src` directory with an empty `index.js` file to contain the entrypoint to your code: + [source, bash] ---- @@ -138,7 +142,7 @@ mkdir src touch src/index.js ---- + -. Replace the default `scripts` entry in your `package.json` file with the following `scripts` entry: +. Replace the default `scripts` entry in your `package.json` file with the following: + [source, json] ---- @@ -150,18 +154,19 @@ touch src/index.js // other dependencies } ---- +===== ==== - -*Install dependencies* - -The following dependencies are required for this guide: - ++ +. This guide requires the installation of the following dependencies: ++ * `@apollo/server`, the library for Apollo Server, is used in this guide to host the subgraph. * `@neo4j/graphql` is the Neo4j GraphQL Library, which translates GraphQL into Cypher and returns the results. * `graphql` is the reference implementation of the GraphQL specification. It is required for `@neo4j/graphql` to function. * `neo4j-driver` is the library for the Neo4j driver, which is required to execute Cypher queries against the database. -Install these dependencies by running the following command: ++ + +Install them by running this command: [source, bash] ---- @@ -184,8 +189,11 @@ const typeDefs = `#graphql `; ---- -Note that this example only includes the Federation `@key` directive. +[NOTE] +=== +This example only includes the Federation `@key` directive. To use more https://www.apollographql.com/docs/federation/federated-types/federated-directives[Federation directives], add them to the `import` array. +=== == Define an entity @@ -210,7 +218,7 @@ The Federation gateway expects each key to resolve to one result, so it is good == Generate a subgraph schema When using the Neo4j GraphQL Library, generating the subgraph schema can be achieved by calling `getSubgraphSchema` instead of `getSchema`. -In other words, only the following line needs to be changed: +For that, the following line needs to be changed: [source, javascript] ---- @@ -257,7 +265,6 @@ const { url } = await startStandaloneServer(server); console.log(`šŸš€ Server ready at ${url}`); ---- - For further iteration, this subgraph can also be composed into a supergraph. Check Apollo's guides for more instructions: diff --git a/modules/ROOT/pages/subscriptions/events.adoc b/modules/ROOT/pages/subscriptions/events.adoc index 5f7516ef..155d0b38 100644 --- a/modules/ROOT/pages/subscriptions/events.adoc +++ b/modules/ROOT/pages/subscriptions/events.adoc @@ -1,19 +1,42 @@ [[subscription-events]] = Subscription events +:description: This page covers a variety of subscription options offered by the Neo4j GraphQL Library. :page-aliases: subscriptions/events/create.adoc, \ subscriptions/events/create_relationship.adoc, subscriptions/events/delete.adoc, subscriptions/events/delete_relationship.adoc, \ subscriptions/events/update.adoc -:description: This page covers a variety of subscription options offered by the Neo4j GraphQL Library. - This page covers a variety of subscription options offered by the Neo4j GraphQL Library. [NOTE] -==== +=== Only changes made through `@neo4j/graphql` should trigger the events here described. -Changes made directly to the database or using the xref::type-definitions/directives/cypher/[`@cypher` directive] will **not** trigger any event. -==== +Changes made directly to the database or using the xref::type-definitions/directives/cypher.adoc[`@cypher` directive] will **not** trigger any event. +=== + +== `CREATE` + +Subscriptions to `CREATE` events listen *only* to newly created nodes, not new relationships. +In this case, a new event is triggered for each new node, containing its properties. + +This action is performed with the top-level subscription `[type]Created`, which contains the following fields: + +* `event`: the event triggering this subscription (in this case, `CREATE`). +* `created`: top-level properties of the newly created node, without relationships. +* `timestamp`: the timestamp in which the mutation was made. +If a same query triggers multiple events, they should have the same timestamp. + +As an example, consider the following type definitions: + +[source,graphql,indent=0] +---- +type Movie { + title: String + genre: String +} +---- +Note, however, that only changes made through `@neo4j/graphql` should trigger events. +Changes made directly to the database or using the xref::type-definitions/directives/cypher.adoc[`@cypher` directive] will **not** trigger any event. == `CREATE` From bfb42e67ce82b8aa6911d1388d93a7060d652644 Mon Sep 17 00:00:00 2001 From: Lidia Zuin <102308961+lidiazuin@users.noreply.github.com> Date: Tue, 26 Sep 2023 14:46:56 +0200 Subject: [PATCH 14/28] Fix scalar description placeholders #38 (#39) * Editorial review of the most recent changes * reverting changes to partials * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * fixing note formatting * Editorial review of the most recent changes * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * Fix scalar description placeholders (#38) --------- Co-authored-by: Neil Dewhurst Co-authored-by: Darrell Warde <8117355+darrellwarde@users.noreply.github.com> --- .../authorization.adoc | 6 +-- .../index.adoc | 4 +- .../pages/integrations/apollo-federation.adoc | 41 +++++++++++-------- modules/ROOT/pages/subscriptions/events.adoc | 22 +++++----- modules/ROOT/partials/reusing-content.adoc | 1 + 5 files changed, 40 insertions(+), 34 deletions(-) diff --git a/modules/ROOT/pages/authentication-and-authorization/authorization.adoc b/modules/ROOT/pages/authentication-and-authorization/authorization.adoc index 4fab80aa..95e1784d 100644 --- a/modules/ROOT/pages/authentication-and-authorization/authorization.adoc +++ b/modules/ROOT/pages/authentication-and-authorization/authorization.adoc @@ -2,8 +2,8 @@ :description: This page describes how to set up authorization features in the Neo4j GraphQL Library. = Authorization -Authorization rules cover what specific data a generated Cypher query is executed against. -They use predicates to evaluate the Cypher generated from a GraphQL query, thus allowing or disallowing execution within the context of nodes and their properties. +Authorization rules cover to what specific data a generated Cypher query is executed against. +They use predicates to evaluate the Cypher generated from a GraphQL query, thus allowing or not execution within the context of nodes and their properties. All authorization rules have an implied requirement for authentication, given that the rules are normally evaluated against values in the JWT payload. @@ -63,7 +63,7 @@ type User @authorization(validate: [ Authentication is implicitly required for every authorization check by default, but this can be disabled on a per-rule basis. This could be the case, for instance, when a node has a property which flags whether the node should be public or not. -For instance, in the case where some `Post` nodes are private and belong to a particular `User`, while other `Post` nodes are public and readable by any user, here is how to set this up: +For instance, in case `Post` nodes might be private and belong to a particular `User`, or be public and readable by any user, here is how to set this up: [source, graphql, indent=0] ---- diff --git a/modules/ROOT/pages/authentication-and-authorization/index.adoc b/modules/ROOT/pages/authentication-and-authorization/index.adoc index f51c7a3e..de1e16d7 100644 --- a/modules/ROOT/pages/authentication-and-authorization/index.adoc +++ b/modules/ROOT/pages/authentication-and-authorization/index.adoc @@ -6,10 +6,10 @@ auth/authorization/allow.adoc, auth/authorization/bind.adoc, auth/authorization/ auth/authorization/where.adoc, guides/v4-migration/authorization.adoc [WARNING] -=== +==== The `@auth` directive has been replaced by `@authentication` and `@authorization`. See the xref::migration/v4-migration/authorization.adoc[Migration guide] for details on how to upgrade. -=== +==== * xref::authentication-and-authorization/authentication.adoc[Authentication] - Explicit authentication, configured using the `@authentication` directive. * xref::authentication-and-authorization/authorization.adoc[Authorization] - Authorization rules set using the `@authorization` directive. diff --git a/modules/ROOT/pages/integrations/apollo-federation.adoc b/modules/ROOT/pages/integrations/apollo-federation.adoc index 0882c0fc..5011d2a4 100644 --- a/modules/ROOT/pages/integrations/apollo-federation.adoc +++ b/modules/ROOT/pages/integrations/apollo-federation.adoc @@ -1,4 +1,5 @@ [[apollo-federation]] +:description: This guide shows how to create a subgraph using the Neo4j GraphQL Library. = Apollo Federation :page-aliases: guides/apollo-federation.adoc :description: This guide shows how to create a subgraph using the Neo4j GraphQL Library, for composition into a supergraph using Apollo Federation. @@ -65,18 +66,19 @@ npm pkg set type="module" ---- + [NOTE] -=== +==== This how-to guide sets up the project using ES Modules, which allows the usage of features such as top-level `await`. -=== +==== . Choose TypeScript or JavaScript to proceed with the setup: -+ + [.tabbed-example] ==== [.include-with-Typescript] ===== -. Create a `src` directory with an empty `index.ts` file to contain the entrypoint to your code: + +. Create a src directory with an empty index.ts file to contain the entrypoint to your code: + [source, bash] ---- @@ -91,14 +93,14 @@ touch src/index.ts npm install --save-dev typescript @types/node @tsconfig/node-lts ---- + -. Create an empty `tsconfig.json` file containing the compiler configuration for TypeScript: +. Create an empty tsconfig.json file containing the compiler configuration for TypeScript: + [source, bash] ---- touch tsconfig.json ---- + -. Add the following configuration to the `tsconfig.json` file: +. Add the following configuration to the tsconfig.json file: + [source, json] ---- @@ -112,12 +114,12 @@ touch tsconfig.json ---- + [NOTE] -=== -This configuration extends the https://github.com/tsconfig/bases#node-lts-tsconfigjson[community base for Node.js LTS], provided by the `@tsconfig/node-lts` package installed above. +====== +This configuration extends the https://github.com/tsconfig/bases#node-lts-tsconfigjson[community base for Node.js LTS], provided by the @tsconfig/node-lts package installed above. For more information on the available options, see the https://www.typescriptlang.org/tsconfig[TypeScript Compiler docs]. -=== +====== + -. Replace the default `scripts` entry in your `package.json` file with the following: +. Replace the default scripts entry in your package.json file with the following: + [source, json] ---- @@ -134,16 +136,17 @@ For more information on the available options, see the https://www.typescriptlan [.include-with-JavaScript] ===== -. Create a `src` directory with an empty `index.js` file to contain the entrypoint to your code: -+ + +Create a `src` directory with an empty `index.js` file to contain the entrypoint to your code: + [source, bash] ---- mkdir src touch src/index.js ---- -+ -. Replace the default `scripts` entry in your `package.json` file with the following: -+ + +Replace the default `scripts` entry in your `package.json` file with the following: + [source, json] ---- { @@ -155,7 +158,9 @@ touch src/index.js } ---- ===== + ==== + + . This guide requires the installation of the following dependencies: + @@ -190,10 +195,10 @@ const typeDefs = `#graphql ---- [NOTE] -=== +==== This example only includes the Federation `@key` directive. To use more https://www.apollographql.com/docs/federation/federated-types/federated-directives[Federation directives], add them to the `import` array. -=== +==== == Define an entity @@ -269,4 +274,4 @@ For further iteration, this subgraph can also be composed into a supergraph. Check Apollo's guides for more instructions: * https://www.apollographql.com/docs/federation/quickstart/studio-composition[Composition in Apollo Studio] -* https://www.apollographql.com/docs/federation/quickstart/local-composition[Local composition] +* https://www.apollographql.com/docs/federation/quickstart/local-composition[Local composition] \ No newline at end of file diff --git a/modules/ROOT/pages/subscriptions/events.adoc b/modules/ROOT/pages/subscriptions/events.adoc index 155d0b38..644da9fb 100644 --- a/modules/ROOT/pages/subscriptions/events.adoc +++ b/modules/ROOT/pages/subscriptions/events.adoc @@ -16,7 +16,7 @@ Changes made directly to the database or using the xref::type-definitions/direct == `CREATE` Subscriptions to `CREATE` events listen *only* to newly created nodes, not new relationships. -In this case, a new event is triggered for each new node, containing its properties. +In this occasion, a new event is triggered for each new node, containing its properties. This action is performed with the top-level subscription `[type]Created`, which contains the following fields: @@ -160,9 +160,9 @@ subscription { Subscriptions to `CREATE_RELATIONSHIP` events listen for newly created relationships to a node of the specified type. [NOTE] -=== +==== This subscription operation is **only** available for types that define relationship fields. -=== +==== == `CREATE_RELATIONSHIP` @@ -175,10 +175,10 @@ These events: * Contain the properties of the nodes connected through the relationship, as well as the properties of the new relationship, if any. [NOTE] -=== +==== Connected nodes that may or may not have previously existed are not covered by this subscription. To subscribe to these nodes' updates, use the xref:subscriptions/events.adoc#_create[`CREATE`] or the xref:subscriptions/events.adoc#_update[`UPDATE`] subscription. -=== +==== Subscriptions to `CREATE_RELATIONSHIP` events can be made with the top-level subscription `[type]RelationshipCreated`, which contains the following fields: @@ -192,10 +192,10 @@ While any event unrelated to `relationshipFieldName` should be `null`, the ones Only top-level properties, without relationships, are available and they are the properties that already existed before the `CREATE_RELATIONSHIP` operation took place. [NOTE] -=== +==== Irrespective of the relationship direction in the database, the `CREATE_RELATIONSHIP` event is bound to the type targeted for the subscription. Consequently, if types A and B have xref:subscriptions/events.adoc#create-non-reciprocal-relationships[non-reciprocal relationships] and a GraphQL operation creates a relationship between them (despite being already previously connected in the database), the `CREATE_RELATIONSHIP` event should only return the subscription to the type A. -=== +==== As an example, consider the following type definitions: @@ -1007,10 +1007,10 @@ This object should be populated with properties according to the deleted relatio * Contains the properties of the nodes connected through the relationship, as well as the properties of the newly deleted relationship, if any. [NOTE] -=== +==== Disconnected nodes that may or may not have been deleted in the process are not covered by this subscription. To subscribe to these nodes' updates, use the `DELETE` subscriptions. -=== +==== Subscriptions to `DELETE_RELATIONSHIP` events can be made with the top-level subscription `[type]RelationshipDeleted`, which contains the following fields: @@ -1023,10 +1023,10 @@ While any event unrelated to `relationshipFieldName` should be `null`, the ones Only top-level properties, without relationships, are available and they are the properties that already existed before the `DELETE_RELATIONSHIP` operation took place. [NOTE] -=== +==== Irrespective of the relationship direction in the database, the `DELETE_RELATIONSHIP` event is bound to the type targeted for the subscription. Consequently, if types A and B have xref:subscriptions/events.adoc#delete-non-reciprocal-relationships[non-reciprocal relationships] and a GraphQL operation deletes a relationship between them (despite being already previously diconnected in the database), the `DELETE_RELATIONSHIP` event should only return the subscription to the type A. -=== +==== As an example, consider these type definitions: diff --git a/modules/ROOT/partials/reusing-content.adoc b/modules/ROOT/partials/reusing-content.adoc index 464c8ddc..52df337f 100644 --- a/modules/ROOT/partials/reusing-content.adoc +++ b/modules/ROOT/partials/reusing-content.adoc @@ -1 +1,2 @@ +[[include-typescript]] This paragraph can be used anywhere with the syntax shown in xref:content-types.adoc#_partials[]. \ No newline at end of file From 7d8aed785739e5e128b95ee820d9a249166109d0 Mon Sep 17 00:00:00 2001 From: lidiazuin Date: Tue, 5 Sep 2023 15:34:54 +0200 Subject: [PATCH 15/28] Editorial review of the most recent changes --- modules/ROOT/pages/authentication-and-authorization/index.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/pages/authentication-and-authorization/index.adoc b/modules/ROOT/pages/authentication-and-authorization/index.adoc index de1e16d7..833024ed 100644 --- a/modules/ROOT/pages/authentication-and-authorization/index.adoc +++ b/modules/ROOT/pages/authentication-and-authorization/index.adoc @@ -1,4 +1,4 @@ -= Authentication and Authorization += Authentication and authorization :description: This section covers authentication andd authorization features in the Neo4j GraphQL Library. :page-aliases: auth/index.adoc, auth/setup.adoc, auth/authentication.adoc, \ auth/authorization.adoc, auth/auth-directive.adoc, auth/subscriptions.adoc, \ From 3759bcf112f7a00eee82ab490568272bb921b572 Mon Sep 17 00:00:00 2001 From: lidiazuin Date: Wed, 6 Sep 2023 11:04:34 +0200 Subject: [PATCH 16/28] reverting changes to partials --- modules/ROOT/partials/reusing-content.adoc | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/ROOT/partials/reusing-content.adoc b/modules/ROOT/partials/reusing-content.adoc index 52df337f..464c8ddc 100644 --- a/modules/ROOT/partials/reusing-content.adoc +++ b/modules/ROOT/partials/reusing-content.adoc @@ -1,2 +1 @@ -[[include-typescript]] This paragraph can be used anywhere with the syntax shown in xref:content-types.adoc#_partials[]. \ No newline at end of file From c827c863bb2b9d240db27aecb56629cb9fa25088 Mon Sep 17 00:00:00 2001 From: lidiazuin Date: Wed, 6 Sep 2023 16:08:12 +0200 Subject: [PATCH 17/28] fixing note formatting --- modules/ROOT/pages/subscriptions/events.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ROOT/pages/subscriptions/events.adoc b/modules/ROOT/pages/subscriptions/events.adoc index 644da9fb..fffe3756 100644 --- a/modules/ROOT/pages/subscriptions/events.adoc +++ b/modules/ROOT/pages/subscriptions/events.adoc @@ -8,10 +8,10 @@ subscriptions/events/update.adoc This page covers a variety of subscription options offered by the Neo4j GraphQL Library. [NOTE] -=== +==== Only changes made through `@neo4j/graphql` should trigger the events here described. Changes made directly to the database or using the xref::type-definitions/directives/cypher.adoc[`@cypher` directive] will **not** trigger any event. -=== +==== == `CREATE` From b5717bf1b82b038287d41dbda5118e5055465fba Mon Sep 17 00:00:00 2001 From: Lidia Zuin <102308961+lidiazuin@users.noreply.github.com> Date: Fri, 15 Sep 2023 11:12:11 +0200 Subject: [PATCH 18/28] Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst --- .../index.adoc | 2 +- .../pages/reference/directives/cypher.adoc | 229 ++++++++++++++++++ .../pages/reference/directives/index.adoc | 161 ++++++++++++ .../type-configuration.adoc | 156 ++++++++++++ .../type-definitions/interfaces.adoc | 135 +++++++++++ modules/ROOT/pages/subscriptions/events.adoc | 27 +-- 6 files changed, 683 insertions(+), 27 deletions(-) create mode 100644 modules/ROOT/pages/reference/directives/cypher.adoc create mode 100644 modules/ROOT/pages/reference/directives/index.adoc create mode 100644 modules/ROOT/pages/reference/directives/schema-configuration/type-configuration.adoc create mode 100644 modules/ROOT/pages/reference/type-definitions/interfaces.adoc diff --git a/modules/ROOT/pages/authentication-and-authorization/index.adoc b/modules/ROOT/pages/authentication-and-authorization/index.adoc index 833024ed..de1e16d7 100644 --- a/modules/ROOT/pages/authentication-and-authorization/index.adoc +++ b/modules/ROOT/pages/authentication-and-authorization/index.adoc @@ -1,4 +1,4 @@ -= Authentication and authorization += Authentication and Authorization :description: This section covers authentication andd authorization features in the Neo4j GraphQL Library. :page-aliases: auth/index.adoc, auth/setup.adoc, auth/authentication.adoc, \ auth/authorization.adoc, auth/auth-directive.adoc, auth/subscriptions.adoc, \ diff --git a/modules/ROOT/pages/reference/directives/cypher.adoc b/modules/ROOT/pages/reference/directives/cypher.adoc new file mode 100644 index 00000000..390d1351 --- /dev/null +++ b/modules/ROOT/pages/reference/directives/cypher.adoc @@ -0,0 +1,229 @@ +[[type-definitions-cypher]] += `@cypher` directive +:page-aliases: type-definitions/cypher.adoc + +The `@cypher` directive binds a GraphQL field to the result(s) of a Cypher query. + +This directive can be used both for properties in a type or as top level queries: + +== Definition + +[source, graphql, indent=0] +---- +"""Instructs @neo4j/graphql to run the specified Cypher statement in order to resolve the value of the field to which the directive is applied.""" +directive @cypher( + """The Cypher statement to run which returns a value of the same type composition as the field definition on which the directive is applied.""" + statement: String!, + """Name of the returned variable from the Cypher statement.""" + columnName: String! +) on FIELD_DEFINITION +---- + + +== Globals + +Global variables are available for use within the Cypher statement. + +=== `this` + +The value `this` is a reference to the currently resolved node, and it can be used to traverse the graph. + +This can be seen in the usage example xref::/type-definitions/directives/cypher.adoc#type-definitions-cypher-object-usage[On an object type field] below. + +=== `auth` + +The value `auth` is represented by the following TypeScript interface definition: + +[source, typescript, indent=0] +---- +interface Auth { + isAuthenticated: boolean; + roles?: string[]; + jwt: any; +} +---- + +For example, you could use the JWT in the request to return the value of the currently logged in User: + +[source, graphql, indent=0] +---- +type User { + id: String +} + +type Query { + me: User @cypher( + statement: """ + MATCH (user:User {id: $jwt.sub}) + RETURN user + """, + columnName: "user" + ) +} +---- + + +=== `cypherParams` +Use to inject values into the cypher query from the GraphQL context function. + +Inject into context: + +[source, typescript, indent=0] +---- +const server = new ApolloServer({ + typeDefs, +}); + +await startStandaloneServer(server, { + context: async ({ req }) => ({ cypherParams: { userId: "user-id-01" } }), +}); +---- + +Use in cypher query: + +[source, graphql, indent=0] +---- +type Query { + userPosts: [Post] @cypher(statement: """ + MATCH (:User {id: $userId})-[:POSTED]->(p:Post) + RETURN p + """, columnName: "p") +} +---- + +== Return values + +The return value of the Cypher statement must be of the same type to which the directive is applied. + +The variable should also be aliased with a name that must be the same as the named passed to `columnName`, this can be the name of a node or relationship query or an alias in the `RETURN` statement of the cypher. + +=== Scalar values + +The Cypher statement must return a value which matches the scalar type to which the directive was applied. + +[source, graphql, indent=0] +---- +type Query { + randomNumber: Int @cypher(statement: "RETURN rand() as result", columnName: "result") +} +---- + +=== Object types + +When returning an object type, all fields of the type must be available in the Cypher return value. This can be achieved by either returning the entire object from the Cypher query, or returning a map of the fields which are required for the object type. Both approaches are demonstrated below: + +[source, graphql, indent=0] +---- +type User { + id +} + +type Query { + users: [User] + @cypher( + statement: """ + MATCH (u:User) + RETURN u + """, + columnName: "u" + ) +} +---- + +[source, graphql, indent=0] +---- +type User { + id +} + +type Query { + users: [User] @cypher(statement: """ + MATCH (u:User) + RETURN { + id: u.id + } as result + """, columnName: "result") +} +---- + +The downside of the latter approach is that you will need to adjust the return object as you change your object type definition. + +== Usage examples + +[[type-definitions-cypher-object-usage]] +=== On an object type field + +In the example below, a field `similarMovies` is bound to the `Movie` type, to find other movies with an overlap of actors: + +[source, graphql, indent=0] +---- +type Actor { + actorId: ID! + name: String + movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) +} + +type Movie { + movieId: ID! + title: String + description: String + year: Int + actors(limit: Int = 10): [Actor!]! + @relationship(type: "ACTED_IN", direction: IN) + similarMovies(limit: Int = 10): [Movie] + @cypher( + statement: """ + MATCH (this)<-[:ACTED_IN]-(:Actor)-[:ACTED_IN]->(rec:Movie) + WITH rec, COUNT(*) AS score ORDER BY score DESC + RETURN rec LIMIT $limit + """, + columnName: "rec" + ) +} +---- + +=== On a query type field + +The example below demonstrates a simple query to return all of the actors in the database: + +[source, graphql, indent=0] +---- +type Actor { + actorId: ID! + name: String +} + +type Query { + allActors: [Actor] + @cypher( + statement: """ + MATCH (a:Actor) + RETURN a + """, + columnName: "a" + ) +} +---- + +=== On a mutation type field + +The example below demonstrates a simple mutation using a Cypher query to insert a single actor with the specified name argument: + +[source, graphql, indent=0] +---- +type Actor { + actorId: ID! + name: String +} + +type Mutation { + createActor(name: String!): Actor + @cypher( + statement: """ + CREATE (a:Actor {name: $name}) + RETURN a + """, + columnName: "a" + ) +} +---- \ No newline at end of file diff --git a/modules/ROOT/pages/reference/directives/index.adoc b/modules/ROOT/pages/reference/directives/index.adoc new file mode 100644 index 00000000..89535425 --- /dev/null +++ b/modules/ROOT/pages/reference/directives/index.adoc @@ -0,0 +1,161 @@ +[[directives]] += Directives +:page-aliases: directives.adoc + +== `@alias` + +The `@alias` directive will map a GraphQL schema field to a Neo4j property on a node or relationship. + +Reference: xref::/type-definitions/directives/database-mapping.adoc#type-definitions-alias[`@alias`] + +== `@coalesce` + +The `@coalesce` directive exposes a mechanism for querying against non-existent, `null` values on a node. + +Reference: xref::/type-definitions/directives/default-values.adoc#type-definitions-default-values-coalesce[`@coalesce`] + +[[custom-resolver-directive]] +== `@customResolver` + +The `@customResolver` directive specifies that a field will be resolved by a custom resolver, and allows the specification +of any required fields that will be passed as arguments to the custom resolver. + +Reference: xref::custom-resolvers.adoc#custom-resolver-directive[`@customResolver`] + +== `@cypher` + +The `@cypher` directive overrides field resolution (including `Query` and `Mutation` fields), instead resolving with the specified Cypher. + +Reference: xref::/type-definitions/directives/cypher.adoc[`@cypher` directive] + +== `@default` + +The `@default` directive allows for the setting of a default value for a field on object creation. + +Reference: xref::/type-definitions/directives/default-values.adoc#type-definitions-default-values-default[`@default`] + +== `@exclude` label:deprecated[] + +This directive is deprecated. + +Use the xref:schema-configuration/type-configuration.adoc#_query[`@query`], xref:schema-configuration/type-configuration.adoc#_mutation[`@mutation`] and the xref:schema-configuration/type-configuration.adoc#_subscription[`@subscription`] directives instead. + +The `@exclude` directive is used on object types to instruct them to be skipped during Query, Mutation and Subscription generation. + +Reference: xref::schema-configuration/type-configuration.adoc#_exclude_deprecated[`@exclude`] + +== `@filterable` + +The `@filterable` directive defines the filters generated for a field. + +Reference: xref:schema-configuration/field-configuration.adoc#_filterable[`@filterable`] + +== `@fulltext` + +The `@fulltext` directive indicates that there should be a Fulltext index inserted into the database for the specified Node and its properties. + +Reference: xref::/type-definitions/directives/indexes-and-constraints.adoc#type-definitions-indexes-fulltext[Fulltext indexes] + +== `@id` + +The `@id` directive marks a field as the unique ID for an object type, and allows for autogeneration of IDs. + +Reference: xref::/type-definitions/directives/autogeneration.adoc#type-definitions-autogeneration-id[`@id`] + +== `@limit` + +The `@limit` is to be used on nodes, and when applied will inject values into Cypher `LIMIT` clauses. + +Reference: xref::/type-definitions/directives/default-values.adoc#type-definitions-default-values-limit[`@limit`] + +== `@mutation` + +This directive is used to limit the availability of Mutation operations in the library. + +Reference: xref:schema-configuration/type-configuration.adoc#_mutation[`@mutation`] + +== `@node` + +The `@node` directive is used to specify the configuration of a GraphQL object type which represents a Neo4j node. + +Reference: xref::/type-definitions/directives/database-mapping.adoc#type-definitions-node[`@node`] + +[[plural-directive]] +== `@plural` + +The `@plural` directive redefines how to compose the plural of the type for the generated operations. +This is particularly useful for types that are not correctly pluralized or are non-English words. + +Reference: xref::/type-definitions/directives/database-mapping.adoc#type-definitions-plural[`@plural`] + +[[populated-by-directive]] +== `@populatedBy` + +The `@populatedBy` directive is used to specify a callback function that gets executed during GraphQL query parsing, +to populate fields which have not been provided within the input. + +Reference: xref::/type-definitions/directives/autogeneration.adoc#type-definitions-autogeneration-populated-by[`@populatedBy`] + +== `@private` + +The `@private` directive protects fields which should only be available through the xref::ogm/index.adoc[OGM]. + +Reference: xref::ogm/private.adoc[`@private` Directive] + +== `@query` + +This directive is used to limit the availability of Query operations in the library. + +Reference: xref:schema-configuration/type-configuration.adoc#_query[`@query`] + +== `@relationship` + +The `@relationship` directive is used to configure relationships between object types. + +Reference: xref::/type-definitions/types/relationships.adoc[Relationships], xref::schema-configuration/field-configuration.adoc#_relationship[`@relationship`] + +== `@relationshipProperties` + +Required to help you distinguish between interfaces which are used for relationship properties, and otherwise. + +Can only be used on interfaces, as per its definition: + +[source, graphql, indent=0] +---- +"""Required to differentiate between interfaces for relationship properties, and otherwise.""" +directive @relationshipProperties on INTERFACE +---- + +== `@relayId` + +The `@relayId` directive can be used on object type fields, to flag which field should be used as the global node identifier for Relay. Can be used once per type. The use of the `@relayId` directive ensures a unique node property constraint for the field. + +== `@selectable` + +The `@selectable` directive sets the availability of fields on queries and aggregations. + +Reference: xref:schema-configuration/field-configuration.adoc#_selectable[`@selectable`] + +== `@settable` + +The `@settable` directive sets the availability of fields on the create and update inputs. + +Reference: xref:schema-configuration/field-configuration.adoc#_settable[`@settable`] + +== `@subscription` + +This directive is used to limit Subscription events available in the library. + +Reference: xref:schema-configuration/type-configuration.adoc#_subscription[`@subscription`] + +== `@timestamp` + +The `@timestamp` directive flags fields to be used to store timestamps on create/update events. + +Reference: xref::/type-definitions/directives/autogeneration.adoc#type-definitions-autogeneration-timestamp[`@timestamp`] + +== `@unique` + +The `@unique` directive indicates that there should be a uniqueness constraint in the database for the fields that it is applied to. + +Reference: xref::/type-definitions/directives/indexes-and-constraints.adoc#type-definitions-constraints-unique[Unique node property constraints] diff --git a/modules/ROOT/pages/reference/directives/schema-configuration/type-configuration.adoc b/modules/ROOT/pages/reference/directives/schema-configuration/type-configuration.adoc new file mode 100644 index 00000000..0d0a5e46 --- /dev/null +++ b/modules/ROOT/pages/reference/directives/schema-configuration/type-configuration.adoc @@ -0,0 +1,156 @@ +[[schema-configuration-type-configuration]] += Type Configuration + + +When representing a Neo4j Node, a GraphQL Object type produces multiple operation fields in the `Query`, `Mutation`, and `Subscription` types. +For example: + +[source, graphql, indent=0] +---- +type Movie { + title: String + length: Int +} +---- + +From these type definitions, the library generates the following operation fields: + +**Query**: + + * `movies` + * `moviesAggregate` + * `moviesConnection` + +**Mutation**: + + * `createMovies` + * `deleteMovies` + * `updateMovies` + +**Subscription**: + + * `movieCreated` + * `movieUpdated` + * `movieDeleted` +. + +This section explains how to reduce the operation fields produced using the directives `@query`, `@mutation`, and `@subscription`. + +== `@query` + +This directive is used to limit the availability of query operations in the library. + +=== Definition + +[source, graphql, indent=0] +---- +directive @query(read: Boolean! = true, aggregate: Boolean! = false) on OBJECT | SCHEMA +---- + +[NOTE] +==== +Aggregations will no longer be generated by default in the 4.0.0 version of the library. +See xref:migration/v4-migration/index.adoc#opt-in-aggregation[`Opt-in Aggregation`] for more information. +==== + +=== Usage + +==== Disable _movies_ and _moviesConnection_ operations + +[source, graphql, indent=0] +---- +type Movie @query(read: false, aggregate: true) { + title: String + length: Int +} +---- + +==== Disable _moviesAggregate_ operations + +[source, graphql, indent=0] +---- +type Movie @query(read: true, aggregate: false) { + title: String + length: Int +} +---- + +== `@mutation` + +This directive is used to limit the availability of Mutation operations in the library. + +=== Definition + +[source, graphql, indent=0] +---- +enum MutationFields { + CREATE + UPDATE + DELETE +} + +directive @mutation(operations: [MutationFields!]! = [CREATE, UPDATE, DELETE]) on OBJECT | SCHEMA +---- + +=== Usage + +==== Disable Create, Delete, and Update operations for _Movie_ + +[source, graphql, indent=0] +---- +type Movie @mutation(operations: []) { + title: String + length: Int +} +---- + +==== Enable only Create operations for _Movie_ + +[source, graphql, indent=0] +---- +type Movie @mutation(operations: [CREATE]) { + title: String + length: Int +} +---- + +== `@subscription` + +This directive is used to limit Subscription operations in the library. + +=== Definition + +[source, graphql, indent=0] +---- +enum SubscriptionEvent { + CREATED + UPDATED + DELETED + RELATIONSHIP_CREATED + RELATIONSHIP_DELETED +} + +directive @subscription(events: [SubscriptionEvent!]! = [CREATED, UPDATED, DELETED, RELATIONSHIP_CREATED, RELATIONSHIP_DELETED]) on OBJECT | SCHEMA +---- + +=== Usage + +==== Disable subscriptions for _Movie_ + +[source, graphql, indent=0] +---- +type Movie @subscription(events: []) { + title: String + length: Int +} +---- + +==== Enable only _movieCreated_ subscription for _Movie_ + +[source, graphql, indent=0] +---- +type Movie @subscription(events: [CREATED]) { + title: String + length: Int +} +---- diff --git a/modules/ROOT/pages/reference/type-definitions/interfaces.adoc b/modules/ROOT/pages/reference/type-definitions/interfaces.adoc new file mode 100644 index 00000000..adc72a27 --- /dev/null +++ b/modules/ROOT/pages/reference/type-definitions/interfaces.adoc @@ -0,0 +1,135 @@ +[[schema-configuration-type-configuration]] += Type Configuration + + +When representing a Neo4j Node, a GraphQL Object type produces multiple operation fields in the `Query`, `Mutation`, and `Subscription` types. +For example: + +[source, graphql, indent=0] +---- +type Movie { + title: String + length: Int +} +---- + +From these type definitions, the library generates the following operation fields: + +**Query**: + + * `movies` + * `moviesAggregate` + * `moviesConnection` + +**Mutation**: + + * `createMovies` + * `deleteMovies` + * `updateMovies` + +**Subscription**: + + * `movieCreated` + * `movieUpdated` + * `movieDeleted` +. + +This section explains how to reduce the operation fields produced using the directives `@query`, `@mutation`, and `@subscription`. + +== `@query` + +This directive is used to limit the availability of query operations in the library. + +=== Definition + +[source, graphql, indent=0] +---- +directive @query(read: Boolean! = true, aggregate: Boolean! = false) on OBJECT | SCHEMA +---- + +[NOTE] +==== +Aggregations will no longer be generated by default in the 4.0.0 version of the library. +See xref:migration/v4-migration/index.adoc#opt-in-aggregation[`Opt-in Aggregation`] for more information. +==== + +[[type-definitions-interfaced-types-querying]] +== Querying an interface + +Which implementations are returned by a query are dictated by the `where` filter applied. + +For example, the following will return all productions with title starting "The " for every actor: + +[source, graphql, indent=0] +---- +enum MutationFields { + CREATE + UPDATE + DELETE +} + +directive @mutation(operations: [MutationFields!]! = [CREATE, UPDATE, DELETE]) on OBJECT | SCHEMA +---- + +=== Usage + +==== Disable Create, Delete, and Update operations for _Movie_ + +[source, graphql, indent=0] +---- +type Movie @mutation(operations: []) { + title: String + length: Int +} +---- + +==== Enable only Create operations for _Movie_ + +[source, graphql, indent=0] +---- +type Movie @mutation(operations: [CREATE]) { + title: String + length: Int +} +---- + +== `@subscription` + +This directive is used to limit Subscription operations in the library. + +=== Definition + +[source, graphql, indent=0] +---- +enum SubscriptionFields { + CREATE + UPDATE + DELETE + CREATE_RELATIONSHIP + DELETE_RELATIONSHIP +} + +directive @subscription(operations: [SubscriptionFields!]! = [CREATE, UPDATE, DELETE, CREATE_RELATIONSHIP, DELETE_RELATIONSHIP]) on OBJECT | SCHEMA +---- + +=== Usage + +==== Disable subscriptions for _Movie_ + +[source, graphql, indent=0] +---- +type Movie @subscription(operations: []) { + title: String + length: Int +} +---- + +==== Enable only _movieCreated_ subscription for _Movie_ + +[source, graphql, indent=0] +---- +type Movie @subscription(operations: [CREATE]) { + title: String + length: Int +} +---- \ No newline at end of file diff --git a/modules/ROOT/pages/subscriptions/events.adoc b/modules/ROOT/pages/subscriptions/events.adoc index fffe3756..d0a856de 100644 --- a/modules/ROOT/pages/subscriptions/events.adoc +++ b/modules/ROOT/pages/subscriptions/events.adoc @@ -10,7 +10,7 @@ This page covers a variety of subscription options offered by the Neo4j GraphQL [NOTE] ==== Only changes made through `@neo4j/graphql` should trigger the events here described. -Changes made directly to the database or using the xref::type-definitions/directives/cypher.adoc[`@cypher` directive] will **not** trigger any event. +Changes made directly to the database or using the xref::reference/directives/cypher.adoc[`@cypher` directive] will **not** trigger any event. ==== == `CREATE` @@ -35,31 +35,6 @@ type Movie { } ---- -Note, however, that only changes made through `@neo4j/graphql` should trigger events. -Changes made directly to the database or using the xref::type-definitions/directives/cypher.adoc[`@cypher` directive] will **not** trigger any event. - -== `CREATE` - -Subscriptions to `CREATE` events listen *only* to newly created nodes, not new relationships. -In this occasion, a new event is triggered for each new node, containing its properties. - -This action is performed with the top-level subscription `[type]Created`, which contains the following fields: - -* `event`: the event triggering this subscription (in this case, `CREATE`). -* `created`: top-level properties of the newly created node, without relationships. -* `timestamp`: the timestamp in which the mutation was made. -If a same query triggers multiple events, they should have the same timestamp. - -As an example, consider the following type definitions: - -[source,graphql,indent=0] ----- -type Movie { - title: String - genre: String -} ----- - A subscription to any newly created node of the `Movie` type should look like this: [source,graphql,indent=0] From 1cf782251b9c6c0a7d69804b621820da23010fd0 Mon Sep 17 00:00:00 2001 From: lidiazuin Date: Tue, 5 Sep 2023 15:34:54 +0200 Subject: [PATCH 19/28] Editorial review of the most recent changes --- .../pages/reference/directives/cypher.adoc | 229 ------------------ .../pages/reference/directives/index.adoc | 161 ------------ .../type-configuration.adoc | 156 ------------ .../type-definitions/interfaces.adoc | 135 ----------- modules/ROOT/pages/subscriptions/events.adoc | 29 ++- modules/ROOT/partials/reusing-content.adoc | 1 + 6 files changed, 28 insertions(+), 683 deletions(-) delete mode 100644 modules/ROOT/pages/reference/directives/cypher.adoc delete mode 100644 modules/ROOT/pages/reference/directives/index.adoc delete mode 100644 modules/ROOT/pages/reference/directives/schema-configuration/type-configuration.adoc delete mode 100644 modules/ROOT/pages/reference/type-definitions/interfaces.adoc diff --git a/modules/ROOT/pages/reference/directives/cypher.adoc b/modules/ROOT/pages/reference/directives/cypher.adoc deleted file mode 100644 index 390d1351..00000000 --- a/modules/ROOT/pages/reference/directives/cypher.adoc +++ /dev/null @@ -1,229 +0,0 @@ -[[type-definitions-cypher]] -= `@cypher` directive -:page-aliases: type-definitions/cypher.adoc - -The `@cypher` directive binds a GraphQL field to the result(s) of a Cypher query. - -This directive can be used both for properties in a type or as top level queries: - -== Definition - -[source, graphql, indent=0] ----- -"""Instructs @neo4j/graphql to run the specified Cypher statement in order to resolve the value of the field to which the directive is applied.""" -directive @cypher( - """The Cypher statement to run which returns a value of the same type composition as the field definition on which the directive is applied.""" - statement: String!, - """Name of the returned variable from the Cypher statement.""" - columnName: String! -) on FIELD_DEFINITION ----- - - -== Globals - -Global variables are available for use within the Cypher statement. - -=== `this` - -The value `this` is a reference to the currently resolved node, and it can be used to traverse the graph. - -This can be seen in the usage example xref::/type-definitions/directives/cypher.adoc#type-definitions-cypher-object-usage[On an object type field] below. - -=== `auth` - -The value `auth` is represented by the following TypeScript interface definition: - -[source, typescript, indent=0] ----- -interface Auth { - isAuthenticated: boolean; - roles?: string[]; - jwt: any; -} ----- - -For example, you could use the JWT in the request to return the value of the currently logged in User: - -[source, graphql, indent=0] ----- -type User { - id: String -} - -type Query { - me: User @cypher( - statement: """ - MATCH (user:User {id: $jwt.sub}) - RETURN user - """, - columnName: "user" - ) -} ----- - - -=== `cypherParams` -Use to inject values into the cypher query from the GraphQL context function. - -Inject into context: - -[source, typescript, indent=0] ----- -const server = new ApolloServer({ - typeDefs, -}); - -await startStandaloneServer(server, { - context: async ({ req }) => ({ cypherParams: { userId: "user-id-01" } }), -}); ----- - -Use in cypher query: - -[source, graphql, indent=0] ----- -type Query { - userPosts: [Post] @cypher(statement: """ - MATCH (:User {id: $userId})-[:POSTED]->(p:Post) - RETURN p - """, columnName: "p") -} ----- - -== Return values - -The return value of the Cypher statement must be of the same type to which the directive is applied. - -The variable should also be aliased with a name that must be the same as the named passed to `columnName`, this can be the name of a node or relationship query or an alias in the `RETURN` statement of the cypher. - -=== Scalar values - -The Cypher statement must return a value which matches the scalar type to which the directive was applied. - -[source, graphql, indent=0] ----- -type Query { - randomNumber: Int @cypher(statement: "RETURN rand() as result", columnName: "result") -} ----- - -=== Object types - -When returning an object type, all fields of the type must be available in the Cypher return value. This can be achieved by either returning the entire object from the Cypher query, or returning a map of the fields which are required for the object type. Both approaches are demonstrated below: - -[source, graphql, indent=0] ----- -type User { - id -} - -type Query { - users: [User] - @cypher( - statement: """ - MATCH (u:User) - RETURN u - """, - columnName: "u" - ) -} ----- - -[source, graphql, indent=0] ----- -type User { - id -} - -type Query { - users: [User] @cypher(statement: """ - MATCH (u:User) - RETURN { - id: u.id - } as result - """, columnName: "result") -} ----- - -The downside of the latter approach is that you will need to adjust the return object as you change your object type definition. - -== Usage examples - -[[type-definitions-cypher-object-usage]] -=== On an object type field - -In the example below, a field `similarMovies` is bound to the `Movie` type, to find other movies with an overlap of actors: - -[source, graphql, indent=0] ----- -type Actor { - actorId: ID! - name: String - movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) -} - -type Movie { - movieId: ID! - title: String - description: String - year: Int - actors(limit: Int = 10): [Actor!]! - @relationship(type: "ACTED_IN", direction: IN) - similarMovies(limit: Int = 10): [Movie] - @cypher( - statement: """ - MATCH (this)<-[:ACTED_IN]-(:Actor)-[:ACTED_IN]->(rec:Movie) - WITH rec, COUNT(*) AS score ORDER BY score DESC - RETURN rec LIMIT $limit - """, - columnName: "rec" - ) -} ----- - -=== On a query type field - -The example below demonstrates a simple query to return all of the actors in the database: - -[source, graphql, indent=0] ----- -type Actor { - actorId: ID! - name: String -} - -type Query { - allActors: [Actor] - @cypher( - statement: """ - MATCH (a:Actor) - RETURN a - """, - columnName: "a" - ) -} ----- - -=== On a mutation type field - -The example below demonstrates a simple mutation using a Cypher query to insert a single actor with the specified name argument: - -[source, graphql, indent=0] ----- -type Actor { - actorId: ID! - name: String -} - -type Mutation { - createActor(name: String!): Actor - @cypher( - statement: """ - CREATE (a:Actor {name: $name}) - RETURN a - """, - columnName: "a" - ) -} ----- \ No newline at end of file diff --git a/modules/ROOT/pages/reference/directives/index.adoc b/modules/ROOT/pages/reference/directives/index.adoc deleted file mode 100644 index 89535425..00000000 --- a/modules/ROOT/pages/reference/directives/index.adoc +++ /dev/null @@ -1,161 +0,0 @@ -[[directives]] -= Directives -:page-aliases: directives.adoc - -== `@alias` - -The `@alias` directive will map a GraphQL schema field to a Neo4j property on a node or relationship. - -Reference: xref::/type-definitions/directives/database-mapping.adoc#type-definitions-alias[`@alias`] - -== `@coalesce` - -The `@coalesce` directive exposes a mechanism for querying against non-existent, `null` values on a node. - -Reference: xref::/type-definitions/directives/default-values.adoc#type-definitions-default-values-coalesce[`@coalesce`] - -[[custom-resolver-directive]] -== `@customResolver` - -The `@customResolver` directive specifies that a field will be resolved by a custom resolver, and allows the specification -of any required fields that will be passed as arguments to the custom resolver. - -Reference: xref::custom-resolvers.adoc#custom-resolver-directive[`@customResolver`] - -== `@cypher` - -The `@cypher` directive overrides field resolution (including `Query` and `Mutation` fields), instead resolving with the specified Cypher. - -Reference: xref::/type-definitions/directives/cypher.adoc[`@cypher` directive] - -== `@default` - -The `@default` directive allows for the setting of a default value for a field on object creation. - -Reference: xref::/type-definitions/directives/default-values.adoc#type-definitions-default-values-default[`@default`] - -== `@exclude` label:deprecated[] - -This directive is deprecated. - -Use the xref:schema-configuration/type-configuration.adoc#_query[`@query`], xref:schema-configuration/type-configuration.adoc#_mutation[`@mutation`] and the xref:schema-configuration/type-configuration.adoc#_subscription[`@subscription`] directives instead. - -The `@exclude` directive is used on object types to instruct them to be skipped during Query, Mutation and Subscription generation. - -Reference: xref::schema-configuration/type-configuration.adoc#_exclude_deprecated[`@exclude`] - -== `@filterable` - -The `@filterable` directive defines the filters generated for a field. - -Reference: xref:schema-configuration/field-configuration.adoc#_filterable[`@filterable`] - -== `@fulltext` - -The `@fulltext` directive indicates that there should be a Fulltext index inserted into the database for the specified Node and its properties. - -Reference: xref::/type-definitions/directives/indexes-and-constraints.adoc#type-definitions-indexes-fulltext[Fulltext indexes] - -== `@id` - -The `@id` directive marks a field as the unique ID for an object type, and allows for autogeneration of IDs. - -Reference: xref::/type-definitions/directives/autogeneration.adoc#type-definitions-autogeneration-id[`@id`] - -== `@limit` - -The `@limit` is to be used on nodes, and when applied will inject values into Cypher `LIMIT` clauses. - -Reference: xref::/type-definitions/directives/default-values.adoc#type-definitions-default-values-limit[`@limit`] - -== `@mutation` - -This directive is used to limit the availability of Mutation operations in the library. - -Reference: xref:schema-configuration/type-configuration.adoc#_mutation[`@mutation`] - -== `@node` - -The `@node` directive is used to specify the configuration of a GraphQL object type which represents a Neo4j node. - -Reference: xref::/type-definitions/directives/database-mapping.adoc#type-definitions-node[`@node`] - -[[plural-directive]] -== `@plural` - -The `@plural` directive redefines how to compose the plural of the type for the generated operations. -This is particularly useful for types that are not correctly pluralized or are non-English words. - -Reference: xref::/type-definitions/directives/database-mapping.adoc#type-definitions-plural[`@plural`] - -[[populated-by-directive]] -== `@populatedBy` - -The `@populatedBy` directive is used to specify a callback function that gets executed during GraphQL query parsing, -to populate fields which have not been provided within the input. - -Reference: xref::/type-definitions/directives/autogeneration.adoc#type-definitions-autogeneration-populated-by[`@populatedBy`] - -== `@private` - -The `@private` directive protects fields which should only be available through the xref::ogm/index.adoc[OGM]. - -Reference: xref::ogm/private.adoc[`@private` Directive] - -== `@query` - -This directive is used to limit the availability of Query operations in the library. - -Reference: xref:schema-configuration/type-configuration.adoc#_query[`@query`] - -== `@relationship` - -The `@relationship` directive is used to configure relationships between object types. - -Reference: xref::/type-definitions/types/relationships.adoc[Relationships], xref::schema-configuration/field-configuration.adoc#_relationship[`@relationship`] - -== `@relationshipProperties` - -Required to help you distinguish between interfaces which are used for relationship properties, and otherwise. - -Can only be used on interfaces, as per its definition: - -[source, graphql, indent=0] ----- -"""Required to differentiate between interfaces for relationship properties, and otherwise.""" -directive @relationshipProperties on INTERFACE ----- - -== `@relayId` - -The `@relayId` directive can be used on object type fields, to flag which field should be used as the global node identifier for Relay. Can be used once per type. The use of the `@relayId` directive ensures a unique node property constraint for the field. - -== `@selectable` - -The `@selectable` directive sets the availability of fields on queries and aggregations. - -Reference: xref:schema-configuration/field-configuration.adoc#_selectable[`@selectable`] - -== `@settable` - -The `@settable` directive sets the availability of fields on the create and update inputs. - -Reference: xref:schema-configuration/field-configuration.adoc#_settable[`@settable`] - -== `@subscription` - -This directive is used to limit Subscription events available in the library. - -Reference: xref:schema-configuration/type-configuration.adoc#_subscription[`@subscription`] - -== `@timestamp` - -The `@timestamp` directive flags fields to be used to store timestamps on create/update events. - -Reference: xref::/type-definitions/directives/autogeneration.adoc#type-definitions-autogeneration-timestamp[`@timestamp`] - -== `@unique` - -The `@unique` directive indicates that there should be a uniqueness constraint in the database for the fields that it is applied to. - -Reference: xref::/type-definitions/directives/indexes-and-constraints.adoc#type-definitions-constraints-unique[Unique node property constraints] diff --git a/modules/ROOT/pages/reference/directives/schema-configuration/type-configuration.adoc b/modules/ROOT/pages/reference/directives/schema-configuration/type-configuration.adoc deleted file mode 100644 index 0d0a5e46..00000000 --- a/modules/ROOT/pages/reference/directives/schema-configuration/type-configuration.adoc +++ /dev/null @@ -1,156 +0,0 @@ -[[schema-configuration-type-configuration]] -= Type Configuration - - -When representing a Neo4j Node, a GraphQL Object type produces multiple operation fields in the `Query`, `Mutation`, and `Subscription` types. -For example: - -[source, graphql, indent=0] ----- -type Movie { - title: String - length: Int -} ----- - -From these type definitions, the library generates the following operation fields: - -**Query**: - - * `movies` - * `moviesAggregate` - * `moviesConnection` - -**Mutation**: - - * `createMovies` - * `deleteMovies` - * `updateMovies` - -**Subscription**: - - * `movieCreated` - * `movieUpdated` - * `movieDeleted` -. - -This section explains how to reduce the operation fields produced using the directives `@query`, `@mutation`, and `@subscription`. - -== `@query` - -This directive is used to limit the availability of query operations in the library. - -=== Definition - -[source, graphql, indent=0] ----- -directive @query(read: Boolean! = true, aggregate: Boolean! = false) on OBJECT | SCHEMA ----- - -[NOTE] -==== -Aggregations will no longer be generated by default in the 4.0.0 version of the library. -See xref:migration/v4-migration/index.adoc#opt-in-aggregation[`Opt-in Aggregation`] for more information. -==== - -=== Usage - -==== Disable _movies_ and _moviesConnection_ operations - -[source, graphql, indent=0] ----- -type Movie @query(read: false, aggregate: true) { - title: String - length: Int -} ----- - -==== Disable _moviesAggregate_ operations - -[source, graphql, indent=0] ----- -type Movie @query(read: true, aggregate: false) { - title: String - length: Int -} ----- - -== `@mutation` - -This directive is used to limit the availability of Mutation operations in the library. - -=== Definition - -[source, graphql, indent=0] ----- -enum MutationFields { - CREATE - UPDATE - DELETE -} - -directive @mutation(operations: [MutationFields!]! = [CREATE, UPDATE, DELETE]) on OBJECT | SCHEMA ----- - -=== Usage - -==== Disable Create, Delete, and Update operations for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @mutation(operations: []) { - title: String - length: Int -} ----- - -==== Enable only Create operations for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @mutation(operations: [CREATE]) { - title: String - length: Int -} ----- - -== `@subscription` - -This directive is used to limit Subscription operations in the library. - -=== Definition - -[source, graphql, indent=0] ----- -enum SubscriptionEvent { - CREATED - UPDATED - DELETED - RELATIONSHIP_CREATED - RELATIONSHIP_DELETED -} - -directive @subscription(events: [SubscriptionEvent!]! = [CREATED, UPDATED, DELETED, RELATIONSHIP_CREATED, RELATIONSHIP_DELETED]) on OBJECT | SCHEMA ----- - -=== Usage - -==== Disable subscriptions for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @subscription(events: []) { - title: String - length: Int -} ----- - -==== Enable only _movieCreated_ subscription for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @subscription(events: [CREATED]) { - title: String - length: Int -} ----- diff --git a/modules/ROOT/pages/reference/type-definitions/interfaces.adoc b/modules/ROOT/pages/reference/type-definitions/interfaces.adoc deleted file mode 100644 index adc72a27..00000000 --- a/modules/ROOT/pages/reference/type-definitions/interfaces.adoc +++ /dev/null @@ -1,135 +0,0 @@ -[[schema-configuration-type-configuration]] -= Type Configuration - - -When representing a Neo4j Node, a GraphQL Object type produces multiple operation fields in the `Query`, `Mutation`, and `Subscription` types. -For example: - -[source, graphql, indent=0] ----- -type Movie { - title: String - length: Int -} ----- - -From these type definitions, the library generates the following operation fields: - -**Query**: - - * `movies` - * `moviesAggregate` - * `moviesConnection` - -**Mutation**: - - * `createMovies` - * `deleteMovies` - * `updateMovies` - -**Subscription**: - - * `movieCreated` - * `movieUpdated` - * `movieDeleted` -. - -This section explains how to reduce the operation fields produced using the directives `@query`, `@mutation`, and `@subscription`. - -== `@query` - -This directive is used to limit the availability of query operations in the library. - -=== Definition - -[source, graphql, indent=0] ----- -directive @query(read: Boolean! = true, aggregate: Boolean! = false) on OBJECT | SCHEMA ----- - -[NOTE] -==== -Aggregations will no longer be generated by default in the 4.0.0 version of the library. -See xref:migration/v4-migration/index.adoc#opt-in-aggregation[`Opt-in Aggregation`] for more information. -==== - -[[type-definitions-interfaced-types-querying]] -== Querying an interface - -Which implementations are returned by a query are dictated by the `where` filter applied. - -For example, the following will return all productions with title starting "The " for every actor: - -[source, graphql, indent=0] ----- -enum MutationFields { - CREATE - UPDATE - DELETE -} - -directive @mutation(operations: [MutationFields!]! = [CREATE, UPDATE, DELETE]) on OBJECT | SCHEMA ----- - -=== Usage - -==== Disable Create, Delete, and Update operations for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @mutation(operations: []) { - title: String - length: Int -} ----- - -==== Enable only Create operations for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @mutation(operations: [CREATE]) { - title: String - length: Int -} ----- - -== `@subscription` - -This directive is used to limit Subscription operations in the library. - -=== Definition - -[source, graphql, indent=0] ----- -enum SubscriptionFields { - CREATE - UPDATE - DELETE - CREATE_RELATIONSHIP - DELETE_RELATIONSHIP -} - -directive @subscription(operations: [SubscriptionFields!]! = [CREATE, UPDATE, DELETE, CREATE_RELATIONSHIP, DELETE_RELATIONSHIP]) on OBJECT | SCHEMA ----- - -=== Usage - -==== Disable subscriptions for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @subscription(operations: []) { - title: String - length: Int -} ----- - -==== Enable only _movieCreated_ subscription for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @subscription(operations: [CREATE]) { - title: String - length: Int -} ----- \ No newline at end of file diff --git a/modules/ROOT/pages/subscriptions/events.adoc b/modules/ROOT/pages/subscriptions/events.adoc index d0a856de..d13ed589 100644 --- a/modules/ROOT/pages/subscriptions/events.adoc +++ b/modules/ROOT/pages/subscriptions/events.adoc @@ -10,8 +10,33 @@ This page covers a variety of subscription options offered by the Neo4j GraphQL [NOTE] ==== Only changes made through `@neo4j/graphql` should trigger the events here described. -Changes made directly to the database or using the xref::reference/directives/cypher.adoc[`@cypher` directive] will **not** trigger any event. -==== +Changes made directly to the database or using the xref::type-definitions/directives/cypher.adoc[`@cypher` directive] will **not** trigger any event. +=== + +== `CREATE` + +Subscriptions to `CREATE` events listen *only* to newly created nodes, not new relationships. +In this occasion, a new event is triggered for each new node, containing its properties. + +This action is performed with the top-level subscription `[type]Created`, which contains the following fields: + +* `event`: the event triggering this subscription (in this case, `CREATE`). +* `created`: top-level properties of the newly created node, without relationships. +* `timestamp`: the timestamp in which the mutation was made. +If a same query triggers multiple events, they should have the same timestamp. + +As an example, consider the following type definitions: + +[source,graphql,indent=0] +---- +type Movie { + title: String + genre: String +} +---- + +Note, however, that only changes made through `@neo4j/graphql` should trigger events. +Changes made directly to the database or using the xref::type-definitions/directives/cypher.adoc[`@cypher` directive] will **not** trigger any event. == `CREATE` diff --git a/modules/ROOT/partials/reusing-content.adoc b/modules/ROOT/partials/reusing-content.adoc index 464c8ddc..52df337f 100644 --- a/modules/ROOT/partials/reusing-content.adoc +++ b/modules/ROOT/partials/reusing-content.adoc @@ -1 +1,2 @@ +[[include-typescript]] This paragraph can be used anywhere with the syntax shown in xref:content-types.adoc#_partials[]. \ No newline at end of file From 010fb01259dd92472adcfea586607c4db8861ded Mon Sep 17 00:00:00 2001 From: lidiazuin Date: Wed, 6 Sep 2023 16:08:12 +0200 Subject: [PATCH 20/28] fixing note formatting --- 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 d13ed589..fffe3756 100644 --- a/modules/ROOT/pages/subscriptions/events.adoc +++ b/modules/ROOT/pages/subscriptions/events.adoc @@ -11,7 +11,7 @@ This page covers a variety of subscription options offered by the Neo4j GraphQL ==== Only changes made through `@neo4j/graphql` should trigger the events here described. Changes made directly to the database or using the xref::type-definitions/directives/cypher.adoc[`@cypher` directive] will **not** trigger any event. -=== +==== == `CREATE` From 3a10f4c66d58c7f2558228903e8afa60ba18b017 Mon Sep 17 00:00:00 2001 From: lidiazuin Date: Tue, 5 Sep 2023 15:34:54 +0200 Subject: [PATCH 21/28] Editorial review of the most recent changes --- .../ROOT/pages/authentication-and-authorization/index.adoc | 1 + modules/ROOT/pages/subscriptions/events.adoc | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/ROOT/pages/authentication-and-authorization/index.adoc b/modules/ROOT/pages/authentication-and-authorization/index.adoc index de1e16d7..10adfff4 100644 --- a/modules/ROOT/pages/authentication-and-authorization/index.adoc +++ b/modules/ROOT/pages/authentication-and-authorization/index.adoc @@ -1,3 +1,4 @@ +:description: This section covers authentication andd authorization features in the Neo4j GraphQL Library. = Authentication and Authorization :description: This section covers authentication andd authorization features in the Neo4j GraphQL Library. :page-aliases: auth/index.adoc, auth/setup.adoc, auth/authentication.adoc, \ diff --git a/modules/ROOT/pages/subscriptions/events.adoc b/modules/ROOT/pages/subscriptions/events.adoc index fffe3756..644da9fb 100644 --- a/modules/ROOT/pages/subscriptions/events.adoc +++ b/modules/ROOT/pages/subscriptions/events.adoc @@ -8,10 +8,10 @@ subscriptions/events/update.adoc This page covers a variety of subscription options offered by the Neo4j GraphQL Library. [NOTE] -==== +=== Only changes made through `@neo4j/graphql` should trigger the events here described. Changes made directly to the database or using the xref::type-definitions/directives/cypher.adoc[`@cypher` directive] will **not** trigger any event. -==== +=== == `CREATE` From a5e461f5ec8ff76e6df73287cafed1d4f1baa668 Mon Sep 17 00:00:00 2001 From: lidiazuin Date: Wed, 6 Sep 2023 16:08:12 +0200 Subject: [PATCH 22/28] fixing note formatting --- 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 644da9fb..93b77837 100644 --- a/modules/ROOT/pages/subscriptions/events.adoc +++ b/modules/ROOT/pages/subscriptions/events.adoc @@ -11,7 +11,7 @@ This page covers a variety of subscription options offered by the Neo4j GraphQL === Only changes made through `@neo4j/graphql` should trigger the events here described. Changes made directly to the database or using the xref::type-definitions/directives/cypher.adoc[`@cypher` directive] will **not** trigger any event. -=== +==== == `CREATE` From 1759c9c21e5a9ebd38385d9e70cd6f59282829f9 Mon Sep 17 00:00:00 2001 From: Lidia Zuin <102308961+lidiazuin@users.noreply.github.com> Date: Fri, 15 Sep 2023 11:12:11 +0200 Subject: [PATCH 23/28] Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst --- .../index.adoc | 1 - .../pages/reference/directives/cypher.adoc | 229 ++++++++++++++++++ .../pages/reference/directives/index.adoc | 161 ++++++++++++ .../type-configuration.adoc | 156 ++++++++++++ .../type-definitions/interfaces.adoc | 135 +++++++++++ modules/ROOT/pages/subscriptions/events.adoc | 26 +- 6 files changed, 682 insertions(+), 26 deletions(-) create mode 100644 modules/ROOT/pages/reference/directives/cypher.adoc create mode 100644 modules/ROOT/pages/reference/directives/index.adoc create mode 100644 modules/ROOT/pages/reference/directives/schema-configuration/type-configuration.adoc create mode 100644 modules/ROOT/pages/reference/type-definitions/interfaces.adoc diff --git a/modules/ROOT/pages/authentication-and-authorization/index.adoc b/modules/ROOT/pages/authentication-and-authorization/index.adoc index 10adfff4..de1e16d7 100644 --- a/modules/ROOT/pages/authentication-and-authorization/index.adoc +++ b/modules/ROOT/pages/authentication-and-authorization/index.adoc @@ -1,4 +1,3 @@ -:description: This section covers authentication andd authorization features in the Neo4j GraphQL Library. = Authentication and Authorization :description: This section covers authentication andd authorization features in the Neo4j GraphQL Library. :page-aliases: auth/index.adoc, auth/setup.adoc, auth/authentication.adoc, \ diff --git a/modules/ROOT/pages/reference/directives/cypher.adoc b/modules/ROOT/pages/reference/directives/cypher.adoc new file mode 100644 index 00000000..390d1351 --- /dev/null +++ b/modules/ROOT/pages/reference/directives/cypher.adoc @@ -0,0 +1,229 @@ +[[type-definitions-cypher]] += `@cypher` directive +:page-aliases: type-definitions/cypher.adoc + +The `@cypher` directive binds a GraphQL field to the result(s) of a Cypher query. + +This directive can be used both for properties in a type or as top level queries: + +== Definition + +[source, graphql, indent=0] +---- +"""Instructs @neo4j/graphql to run the specified Cypher statement in order to resolve the value of the field to which the directive is applied.""" +directive @cypher( + """The Cypher statement to run which returns a value of the same type composition as the field definition on which the directive is applied.""" + statement: String!, + """Name of the returned variable from the Cypher statement.""" + columnName: String! +) on FIELD_DEFINITION +---- + + +== Globals + +Global variables are available for use within the Cypher statement. + +=== `this` + +The value `this` is a reference to the currently resolved node, and it can be used to traverse the graph. + +This can be seen in the usage example xref::/type-definitions/directives/cypher.adoc#type-definitions-cypher-object-usage[On an object type field] below. + +=== `auth` + +The value `auth` is represented by the following TypeScript interface definition: + +[source, typescript, indent=0] +---- +interface Auth { + isAuthenticated: boolean; + roles?: string[]; + jwt: any; +} +---- + +For example, you could use the JWT in the request to return the value of the currently logged in User: + +[source, graphql, indent=0] +---- +type User { + id: String +} + +type Query { + me: User @cypher( + statement: """ + MATCH (user:User {id: $jwt.sub}) + RETURN user + """, + columnName: "user" + ) +} +---- + + +=== `cypherParams` +Use to inject values into the cypher query from the GraphQL context function. + +Inject into context: + +[source, typescript, indent=0] +---- +const server = new ApolloServer({ + typeDefs, +}); + +await startStandaloneServer(server, { + context: async ({ req }) => ({ cypherParams: { userId: "user-id-01" } }), +}); +---- + +Use in cypher query: + +[source, graphql, indent=0] +---- +type Query { + userPosts: [Post] @cypher(statement: """ + MATCH (:User {id: $userId})-[:POSTED]->(p:Post) + RETURN p + """, columnName: "p") +} +---- + +== Return values + +The return value of the Cypher statement must be of the same type to which the directive is applied. + +The variable should also be aliased with a name that must be the same as the named passed to `columnName`, this can be the name of a node or relationship query or an alias in the `RETURN` statement of the cypher. + +=== Scalar values + +The Cypher statement must return a value which matches the scalar type to which the directive was applied. + +[source, graphql, indent=0] +---- +type Query { + randomNumber: Int @cypher(statement: "RETURN rand() as result", columnName: "result") +} +---- + +=== Object types + +When returning an object type, all fields of the type must be available in the Cypher return value. This can be achieved by either returning the entire object from the Cypher query, or returning a map of the fields which are required for the object type. Both approaches are demonstrated below: + +[source, graphql, indent=0] +---- +type User { + id +} + +type Query { + users: [User] + @cypher( + statement: """ + MATCH (u:User) + RETURN u + """, + columnName: "u" + ) +} +---- + +[source, graphql, indent=0] +---- +type User { + id +} + +type Query { + users: [User] @cypher(statement: """ + MATCH (u:User) + RETURN { + id: u.id + } as result + """, columnName: "result") +} +---- + +The downside of the latter approach is that you will need to adjust the return object as you change your object type definition. + +== Usage examples + +[[type-definitions-cypher-object-usage]] +=== On an object type field + +In the example below, a field `similarMovies` is bound to the `Movie` type, to find other movies with an overlap of actors: + +[source, graphql, indent=0] +---- +type Actor { + actorId: ID! + name: String + movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) +} + +type Movie { + movieId: ID! + title: String + description: String + year: Int + actors(limit: Int = 10): [Actor!]! + @relationship(type: "ACTED_IN", direction: IN) + similarMovies(limit: Int = 10): [Movie] + @cypher( + statement: """ + MATCH (this)<-[:ACTED_IN]-(:Actor)-[:ACTED_IN]->(rec:Movie) + WITH rec, COUNT(*) AS score ORDER BY score DESC + RETURN rec LIMIT $limit + """, + columnName: "rec" + ) +} +---- + +=== On a query type field + +The example below demonstrates a simple query to return all of the actors in the database: + +[source, graphql, indent=0] +---- +type Actor { + actorId: ID! + name: String +} + +type Query { + allActors: [Actor] + @cypher( + statement: """ + MATCH (a:Actor) + RETURN a + """, + columnName: "a" + ) +} +---- + +=== On a mutation type field + +The example below demonstrates a simple mutation using a Cypher query to insert a single actor with the specified name argument: + +[source, graphql, indent=0] +---- +type Actor { + actorId: ID! + name: String +} + +type Mutation { + createActor(name: String!): Actor + @cypher( + statement: """ + CREATE (a:Actor {name: $name}) + RETURN a + """, + columnName: "a" + ) +} +---- \ No newline at end of file diff --git a/modules/ROOT/pages/reference/directives/index.adoc b/modules/ROOT/pages/reference/directives/index.adoc new file mode 100644 index 00000000..89535425 --- /dev/null +++ b/modules/ROOT/pages/reference/directives/index.adoc @@ -0,0 +1,161 @@ +[[directives]] += Directives +:page-aliases: directives.adoc + +== `@alias` + +The `@alias` directive will map a GraphQL schema field to a Neo4j property on a node or relationship. + +Reference: xref::/type-definitions/directives/database-mapping.adoc#type-definitions-alias[`@alias`] + +== `@coalesce` + +The `@coalesce` directive exposes a mechanism for querying against non-existent, `null` values on a node. + +Reference: xref::/type-definitions/directives/default-values.adoc#type-definitions-default-values-coalesce[`@coalesce`] + +[[custom-resolver-directive]] +== `@customResolver` + +The `@customResolver` directive specifies that a field will be resolved by a custom resolver, and allows the specification +of any required fields that will be passed as arguments to the custom resolver. + +Reference: xref::custom-resolvers.adoc#custom-resolver-directive[`@customResolver`] + +== `@cypher` + +The `@cypher` directive overrides field resolution (including `Query` and `Mutation` fields), instead resolving with the specified Cypher. + +Reference: xref::/type-definitions/directives/cypher.adoc[`@cypher` directive] + +== `@default` + +The `@default` directive allows for the setting of a default value for a field on object creation. + +Reference: xref::/type-definitions/directives/default-values.adoc#type-definitions-default-values-default[`@default`] + +== `@exclude` label:deprecated[] + +This directive is deprecated. + +Use the xref:schema-configuration/type-configuration.adoc#_query[`@query`], xref:schema-configuration/type-configuration.adoc#_mutation[`@mutation`] and the xref:schema-configuration/type-configuration.adoc#_subscription[`@subscription`] directives instead. + +The `@exclude` directive is used on object types to instruct them to be skipped during Query, Mutation and Subscription generation. + +Reference: xref::schema-configuration/type-configuration.adoc#_exclude_deprecated[`@exclude`] + +== `@filterable` + +The `@filterable` directive defines the filters generated for a field. + +Reference: xref:schema-configuration/field-configuration.adoc#_filterable[`@filterable`] + +== `@fulltext` + +The `@fulltext` directive indicates that there should be a Fulltext index inserted into the database for the specified Node and its properties. + +Reference: xref::/type-definitions/directives/indexes-and-constraints.adoc#type-definitions-indexes-fulltext[Fulltext indexes] + +== `@id` + +The `@id` directive marks a field as the unique ID for an object type, and allows for autogeneration of IDs. + +Reference: xref::/type-definitions/directives/autogeneration.adoc#type-definitions-autogeneration-id[`@id`] + +== `@limit` + +The `@limit` is to be used on nodes, and when applied will inject values into Cypher `LIMIT` clauses. + +Reference: xref::/type-definitions/directives/default-values.adoc#type-definitions-default-values-limit[`@limit`] + +== `@mutation` + +This directive is used to limit the availability of Mutation operations in the library. + +Reference: xref:schema-configuration/type-configuration.adoc#_mutation[`@mutation`] + +== `@node` + +The `@node` directive is used to specify the configuration of a GraphQL object type which represents a Neo4j node. + +Reference: xref::/type-definitions/directives/database-mapping.adoc#type-definitions-node[`@node`] + +[[plural-directive]] +== `@plural` + +The `@plural` directive redefines how to compose the plural of the type for the generated operations. +This is particularly useful for types that are not correctly pluralized or are non-English words. + +Reference: xref::/type-definitions/directives/database-mapping.adoc#type-definitions-plural[`@plural`] + +[[populated-by-directive]] +== `@populatedBy` + +The `@populatedBy` directive is used to specify a callback function that gets executed during GraphQL query parsing, +to populate fields which have not been provided within the input. + +Reference: xref::/type-definitions/directives/autogeneration.adoc#type-definitions-autogeneration-populated-by[`@populatedBy`] + +== `@private` + +The `@private` directive protects fields which should only be available through the xref::ogm/index.adoc[OGM]. + +Reference: xref::ogm/private.adoc[`@private` Directive] + +== `@query` + +This directive is used to limit the availability of Query operations in the library. + +Reference: xref:schema-configuration/type-configuration.adoc#_query[`@query`] + +== `@relationship` + +The `@relationship` directive is used to configure relationships between object types. + +Reference: xref::/type-definitions/types/relationships.adoc[Relationships], xref::schema-configuration/field-configuration.adoc#_relationship[`@relationship`] + +== `@relationshipProperties` + +Required to help you distinguish between interfaces which are used for relationship properties, and otherwise. + +Can only be used on interfaces, as per its definition: + +[source, graphql, indent=0] +---- +"""Required to differentiate between interfaces for relationship properties, and otherwise.""" +directive @relationshipProperties on INTERFACE +---- + +== `@relayId` + +The `@relayId` directive can be used on object type fields, to flag which field should be used as the global node identifier for Relay. Can be used once per type. The use of the `@relayId` directive ensures a unique node property constraint for the field. + +== `@selectable` + +The `@selectable` directive sets the availability of fields on queries and aggregations. + +Reference: xref:schema-configuration/field-configuration.adoc#_selectable[`@selectable`] + +== `@settable` + +The `@settable` directive sets the availability of fields on the create and update inputs. + +Reference: xref:schema-configuration/field-configuration.adoc#_settable[`@settable`] + +== `@subscription` + +This directive is used to limit Subscription events available in the library. + +Reference: xref:schema-configuration/type-configuration.adoc#_subscription[`@subscription`] + +== `@timestamp` + +The `@timestamp` directive flags fields to be used to store timestamps on create/update events. + +Reference: xref::/type-definitions/directives/autogeneration.adoc#type-definitions-autogeneration-timestamp[`@timestamp`] + +== `@unique` + +The `@unique` directive indicates that there should be a uniqueness constraint in the database for the fields that it is applied to. + +Reference: xref::/type-definitions/directives/indexes-and-constraints.adoc#type-definitions-constraints-unique[Unique node property constraints] diff --git a/modules/ROOT/pages/reference/directives/schema-configuration/type-configuration.adoc b/modules/ROOT/pages/reference/directives/schema-configuration/type-configuration.adoc new file mode 100644 index 00000000..0d0a5e46 --- /dev/null +++ b/modules/ROOT/pages/reference/directives/schema-configuration/type-configuration.adoc @@ -0,0 +1,156 @@ +[[schema-configuration-type-configuration]] += Type Configuration + + +When representing a Neo4j Node, a GraphQL Object type produces multiple operation fields in the `Query`, `Mutation`, and `Subscription` types. +For example: + +[source, graphql, indent=0] +---- +type Movie { + title: String + length: Int +} +---- + +From these type definitions, the library generates the following operation fields: + +**Query**: + + * `movies` + * `moviesAggregate` + * `moviesConnection` + +**Mutation**: + + * `createMovies` + * `deleteMovies` + * `updateMovies` + +**Subscription**: + + * `movieCreated` + * `movieUpdated` + * `movieDeleted` +. + +This section explains how to reduce the operation fields produced using the directives `@query`, `@mutation`, and `@subscription`. + +== `@query` + +This directive is used to limit the availability of query operations in the library. + +=== Definition + +[source, graphql, indent=0] +---- +directive @query(read: Boolean! = true, aggregate: Boolean! = false) on OBJECT | SCHEMA +---- + +[NOTE] +==== +Aggregations will no longer be generated by default in the 4.0.0 version of the library. +See xref:migration/v4-migration/index.adoc#opt-in-aggregation[`Opt-in Aggregation`] for more information. +==== + +=== Usage + +==== Disable _movies_ and _moviesConnection_ operations + +[source, graphql, indent=0] +---- +type Movie @query(read: false, aggregate: true) { + title: String + length: Int +} +---- + +==== Disable _moviesAggregate_ operations + +[source, graphql, indent=0] +---- +type Movie @query(read: true, aggregate: false) { + title: String + length: Int +} +---- + +== `@mutation` + +This directive is used to limit the availability of Mutation operations in the library. + +=== Definition + +[source, graphql, indent=0] +---- +enum MutationFields { + CREATE + UPDATE + DELETE +} + +directive @mutation(operations: [MutationFields!]! = [CREATE, UPDATE, DELETE]) on OBJECT | SCHEMA +---- + +=== Usage + +==== Disable Create, Delete, and Update operations for _Movie_ + +[source, graphql, indent=0] +---- +type Movie @mutation(operations: []) { + title: String + length: Int +} +---- + +==== Enable only Create operations for _Movie_ + +[source, graphql, indent=0] +---- +type Movie @mutation(operations: [CREATE]) { + title: String + length: Int +} +---- + +== `@subscription` + +This directive is used to limit Subscription operations in the library. + +=== Definition + +[source, graphql, indent=0] +---- +enum SubscriptionEvent { + CREATED + UPDATED + DELETED + RELATIONSHIP_CREATED + RELATIONSHIP_DELETED +} + +directive @subscription(events: [SubscriptionEvent!]! = [CREATED, UPDATED, DELETED, RELATIONSHIP_CREATED, RELATIONSHIP_DELETED]) on OBJECT | SCHEMA +---- + +=== Usage + +==== Disable subscriptions for _Movie_ + +[source, graphql, indent=0] +---- +type Movie @subscription(events: []) { + title: String + length: Int +} +---- + +==== Enable only _movieCreated_ subscription for _Movie_ + +[source, graphql, indent=0] +---- +type Movie @subscription(events: [CREATED]) { + title: String + length: Int +} +---- diff --git a/modules/ROOT/pages/reference/type-definitions/interfaces.adoc b/modules/ROOT/pages/reference/type-definitions/interfaces.adoc new file mode 100644 index 00000000..adc72a27 --- /dev/null +++ b/modules/ROOT/pages/reference/type-definitions/interfaces.adoc @@ -0,0 +1,135 @@ +[[schema-configuration-type-configuration]] += Type Configuration + + +When representing a Neo4j Node, a GraphQL Object type produces multiple operation fields in the `Query`, `Mutation`, and `Subscription` types. +For example: + +[source, graphql, indent=0] +---- +type Movie { + title: String + length: Int +} +---- + +From these type definitions, the library generates the following operation fields: + +**Query**: + + * `movies` + * `moviesAggregate` + * `moviesConnection` + +**Mutation**: + + * `createMovies` + * `deleteMovies` + * `updateMovies` + +**Subscription**: + + * `movieCreated` + * `movieUpdated` + * `movieDeleted` +. + +This section explains how to reduce the operation fields produced using the directives `@query`, `@mutation`, and `@subscription`. + +== `@query` + +This directive is used to limit the availability of query operations in the library. + +=== Definition + +[source, graphql, indent=0] +---- +directive @query(read: Boolean! = true, aggregate: Boolean! = false) on OBJECT | SCHEMA +---- + +[NOTE] +==== +Aggregations will no longer be generated by default in the 4.0.0 version of the library. +See xref:migration/v4-migration/index.adoc#opt-in-aggregation[`Opt-in Aggregation`] for more information. +==== + +[[type-definitions-interfaced-types-querying]] +== Querying an interface + +Which implementations are returned by a query are dictated by the `where` filter applied. + +For example, the following will return all productions with title starting "The " for every actor: + +[source, graphql, indent=0] +---- +enum MutationFields { + CREATE + UPDATE + DELETE +} + +directive @mutation(operations: [MutationFields!]! = [CREATE, UPDATE, DELETE]) on OBJECT | SCHEMA +---- + +=== Usage + +==== Disable Create, Delete, and Update operations for _Movie_ + +[source, graphql, indent=0] +---- +type Movie @mutation(operations: []) { + title: String + length: Int +} +---- + +==== Enable only Create operations for _Movie_ + +[source, graphql, indent=0] +---- +type Movie @mutation(operations: [CREATE]) { + title: String + length: Int +} +---- + +== `@subscription` + +This directive is used to limit Subscription operations in the library. + +=== Definition + +[source, graphql, indent=0] +---- +enum SubscriptionFields { + CREATE + UPDATE + DELETE + CREATE_RELATIONSHIP + DELETE_RELATIONSHIP +} + +directive @subscription(operations: [SubscriptionFields!]! = [CREATE, UPDATE, DELETE, CREATE_RELATIONSHIP, DELETE_RELATIONSHIP]) on OBJECT | SCHEMA +---- + +=== Usage + +==== Disable subscriptions for _Movie_ + +[source, graphql, indent=0] +---- +type Movie @subscription(operations: []) { + title: String + length: Int +} +---- + +==== Enable only _movieCreated_ subscription for _Movie_ + +[source, graphql, indent=0] +---- +type Movie @subscription(operations: [CREATE]) { + title: String + length: Int +} +---- \ No newline at end of file diff --git a/modules/ROOT/pages/subscriptions/events.adoc b/modules/ROOT/pages/subscriptions/events.adoc index 93b77837..18dea0ac 100644 --- a/modules/ROOT/pages/subscriptions/events.adoc +++ b/modules/ROOT/pages/subscriptions/events.adoc @@ -10,33 +10,9 @@ This page covers a variety of subscription options offered by the Neo4j GraphQL [NOTE] === Only changes made through `@neo4j/graphql` should trigger the events here described. -Changes made directly to the database or using the xref::type-definitions/directives/cypher.adoc[`@cypher` directive] will **not** trigger any event. +Changes made directly to the database or using the xref::reference/directives/cypher.adoc[`@cypher` directive] will **not** trigger any event. ==== -== `CREATE` - -Subscriptions to `CREATE` events listen *only* to newly created nodes, not new relationships. -In this occasion, a new event is triggered for each new node, containing its properties. - -This action is performed with the top-level subscription `[type]Created`, which contains the following fields: - -* `event`: the event triggering this subscription (in this case, `CREATE`). -* `created`: top-level properties of the newly created node, without relationships. -* `timestamp`: the timestamp in which the mutation was made. -If a same query triggers multiple events, they should have the same timestamp. - -As an example, consider the following type definitions: - -[source,graphql,indent=0] ----- -type Movie { - title: String - genre: String -} ----- - -Note, however, that only changes made through `@neo4j/graphql` should trigger events. -Changes made directly to the database or using the xref::type-definitions/directives/cypher.adoc[`@cypher` directive] will **not** trigger any event. == `CREATE` From 1b26e46c96513eb74599a8d2e29268a1e913c974 Mon Sep 17 00:00:00 2001 From: lidiazuin Date: Tue, 5 Sep 2023 15:34:54 +0200 Subject: [PATCH 24/28] Editorial review of the most recent changes --- .../pages/reference/directives/cypher.adoc | 229 ------------------ .../pages/reference/directives/index.adoc | 161 ------------ .../type-configuration.adoc | 156 ------------ .../type-definitions/interfaces.adoc | 135 ----------- modules/ROOT/pages/subscriptions/events.adoc | 28 ++- 5 files changed, 26 insertions(+), 683 deletions(-) delete mode 100644 modules/ROOT/pages/reference/directives/cypher.adoc delete mode 100644 modules/ROOT/pages/reference/directives/index.adoc delete mode 100644 modules/ROOT/pages/reference/directives/schema-configuration/type-configuration.adoc delete mode 100644 modules/ROOT/pages/reference/type-definitions/interfaces.adoc diff --git a/modules/ROOT/pages/reference/directives/cypher.adoc b/modules/ROOT/pages/reference/directives/cypher.adoc deleted file mode 100644 index 390d1351..00000000 --- a/modules/ROOT/pages/reference/directives/cypher.adoc +++ /dev/null @@ -1,229 +0,0 @@ -[[type-definitions-cypher]] -= `@cypher` directive -:page-aliases: type-definitions/cypher.adoc - -The `@cypher` directive binds a GraphQL field to the result(s) of a Cypher query. - -This directive can be used both for properties in a type or as top level queries: - -== Definition - -[source, graphql, indent=0] ----- -"""Instructs @neo4j/graphql to run the specified Cypher statement in order to resolve the value of the field to which the directive is applied.""" -directive @cypher( - """The Cypher statement to run which returns a value of the same type composition as the field definition on which the directive is applied.""" - statement: String!, - """Name of the returned variable from the Cypher statement.""" - columnName: String! -) on FIELD_DEFINITION ----- - - -== Globals - -Global variables are available for use within the Cypher statement. - -=== `this` - -The value `this` is a reference to the currently resolved node, and it can be used to traverse the graph. - -This can be seen in the usage example xref::/type-definitions/directives/cypher.adoc#type-definitions-cypher-object-usage[On an object type field] below. - -=== `auth` - -The value `auth` is represented by the following TypeScript interface definition: - -[source, typescript, indent=0] ----- -interface Auth { - isAuthenticated: boolean; - roles?: string[]; - jwt: any; -} ----- - -For example, you could use the JWT in the request to return the value of the currently logged in User: - -[source, graphql, indent=0] ----- -type User { - id: String -} - -type Query { - me: User @cypher( - statement: """ - MATCH (user:User {id: $jwt.sub}) - RETURN user - """, - columnName: "user" - ) -} ----- - - -=== `cypherParams` -Use to inject values into the cypher query from the GraphQL context function. - -Inject into context: - -[source, typescript, indent=0] ----- -const server = new ApolloServer({ - typeDefs, -}); - -await startStandaloneServer(server, { - context: async ({ req }) => ({ cypherParams: { userId: "user-id-01" } }), -}); ----- - -Use in cypher query: - -[source, graphql, indent=0] ----- -type Query { - userPosts: [Post] @cypher(statement: """ - MATCH (:User {id: $userId})-[:POSTED]->(p:Post) - RETURN p - """, columnName: "p") -} ----- - -== Return values - -The return value of the Cypher statement must be of the same type to which the directive is applied. - -The variable should also be aliased with a name that must be the same as the named passed to `columnName`, this can be the name of a node or relationship query or an alias in the `RETURN` statement of the cypher. - -=== Scalar values - -The Cypher statement must return a value which matches the scalar type to which the directive was applied. - -[source, graphql, indent=0] ----- -type Query { - randomNumber: Int @cypher(statement: "RETURN rand() as result", columnName: "result") -} ----- - -=== Object types - -When returning an object type, all fields of the type must be available in the Cypher return value. This can be achieved by either returning the entire object from the Cypher query, or returning a map of the fields which are required for the object type. Both approaches are demonstrated below: - -[source, graphql, indent=0] ----- -type User { - id -} - -type Query { - users: [User] - @cypher( - statement: """ - MATCH (u:User) - RETURN u - """, - columnName: "u" - ) -} ----- - -[source, graphql, indent=0] ----- -type User { - id -} - -type Query { - users: [User] @cypher(statement: """ - MATCH (u:User) - RETURN { - id: u.id - } as result - """, columnName: "result") -} ----- - -The downside of the latter approach is that you will need to adjust the return object as you change your object type definition. - -== Usage examples - -[[type-definitions-cypher-object-usage]] -=== On an object type field - -In the example below, a field `similarMovies` is bound to the `Movie` type, to find other movies with an overlap of actors: - -[source, graphql, indent=0] ----- -type Actor { - actorId: ID! - name: String - movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) -} - -type Movie { - movieId: ID! - title: String - description: String - year: Int - actors(limit: Int = 10): [Actor!]! - @relationship(type: "ACTED_IN", direction: IN) - similarMovies(limit: Int = 10): [Movie] - @cypher( - statement: """ - MATCH (this)<-[:ACTED_IN]-(:Actor)-[:ACTED_IN]->(rec:Movie) - WITH rec, COUNT(*) AS score ORDER BY score DESC - RETURN rec LIMIT $limit - """, - columnName: "rec" - ) -} ----- - -=== On a query type field - -The example below demonstrates a simple query to return all of the actors in the database: - -[source, graphql, indent=0] ----- -type Actor { - actorId: ID! - name: String -} - -type Query { - allActors: [Actor] - @cypher( - statement: """ - MATCH (a:Actor) - RETURN a - """, - columnName: "a" - ) -} ----- - -=== On a mutation type field - -The example below demonstrates a simple mutation using a Cypher query to insert a single actor with the specified name argument: - -[source, graphql, indent=0] ----- -type Actor { - actorId: ID! - name: String -} - -type Mutation { - createActor(name: String!): Actor - @cypher( - statement: """ - CREATE (a:Actor {name: $name}) - RETURN a - """, - columnName: "a" - ) -} ----- \ No newline at end of file diff --git a/modules/ROOT/pages/reference/directives/index.adoc b/modules/ROOT/pages/reference/directives/index.adoc deleted file mode 100644 index 89535425..00000000 --- a/modules/ROOT/pages/reference/directives/index.adoc +++ /dev/null @@ -1,161 +0,0 @@ -[[directives]] -= Directives -:page-aliases: directives.adoc - -== `@alias` - -The `@alias` directive will map a GraphQL schema field to a Neo4j property on a node or relationship. - -Reference: xref::/type-definitions/directives/database-mapping.adoc#type-definitions-alias[`@alias`] - -== `@coalesce` - -The `@coalesce` directive exposes a mechanism for querying against non-existent, `null` values on a node. - -Reference: xref::/type-definitions/directives/default-values.adoc#type-definitions-default-values-coalesce[`@coalesce`] - -[[custom-resolver-directive]] -== `@customResolver` - -The `@customResolver` directive specifies that a field will be resolved by a custom resolver, and allows the specification -of any required fields that will be passed as arguments to the custom resolver. - -Reference: xref::custom-resolvers.adoc#custom-resolver-directive[`@customResolver`] - -== `@cypher` - -The `@cypher` directive overrides field resolution (including `Query` and `Mutation` fields), instead resolving with the specified Cypher. - -Reference: xref::/type-definitions/directives/cypher.adoc[`@cypher` directive] - -== `@default` - -The `@default` directive allows for the setting of a default value for a field on object creation. - -Reference: xref::/type-definitions/directives/default-values.adoc#type-definitions-default-values-default[`@default`] - -== `@exclude` label:deprecated[] - -This directive is deprecated. - -Use the xref:schema-configuration/type-configuration.adoc#_query[`@query`], xref:schema-configuration/type-configuration.adoc#_mutation[`@mutation`] and the xref:schema-configuration/type-configuration.adoc#_subscription[`@subscription`] directives instead. - -The `@exclude` directive is used on object types to instruct them to be skipped during Query, Mutation and Subscription generation. - -Reference: xref::schema-configuration/type-configuration.adoc#_exclude_deprecated[`@exclude`] - -== `@filterable` - -The `@filterable` directive defines the filters generated for a field. - -Reference: xref:schema-configuration/field-configuration.adoc#_filterable[`@filterable`] - -== `@fulltext` - -The `@fulltext` directive indicates that there should be a Fulltext index inserted into the database for the specified Node and its properties. - -Reference: xref::/type-definitions/directives/indexes-and-constraints.adoc#type-definitions-indexes-fulltext[Fulltext indexes] - -== `@id` - -The `@id` directive marks a field as the unique ID for an object type, and allows for autogeneration of IDs. - -Reference: xref::/type-definitions/directives/autogeneration.adoc#type-definitions-autogeneration-id[`@id`] - -== `@limit` - -The `@limit` is to be used on nodes, and when applied will inject values into Cypher `LIMIT` clauses. - -Reference: xref::/type-definitions/directives/default-values.adoc#type-definitions-default-values-limit[`@limit`] - -== `@mutation` - -This directive is used to limit the availability of Mutation operations in the library. - -Reference: xref:schema-configuration/type-configuration.adoc#_mutation[`@mutation`] - -== `@node` - -The `@node` directive is used to specify the configuration of a GraphQL object type which represents a Neo4j node. - -Reference: xref::/type-definitions/directives/database-mapping.adoc#type-definitions-node[`@node`] - -[[plural-directive]] -== `@plural` - -The `@plural` directive redefines how to compose the plural of the type for the generated operations. -This is particularly useful for types that are not correctly pluralized or are non-English words. - -Reference: xref::/type-definitions/directives/database-mapping.adoc#type-definitions-plural[`@plural`] - -[[populated-by-directive]] -== `@populatedBy` - -The `@populatedBy` directive is used to specify a callback function that gets executed during GraphQL query parsing, -to populate fields which have not been provided within the input. - -Reference: xref::/type-definitions/directives/autogeneration.adoc#type-definitions-autogeneration-populated-by[`@populatedBy`] - -== `@private` - -The `@private` directive protects fields which should only be available through the xref::ogm/index.adoc[OGM]. - -Reference: xref::ogm/private.adoc[`@private` Directive] - -== `@query` - -This directive is used to limit the availability of Query operations in the library. - -Reference: xref:schema-configuration/type-configuration.adoc#_query[`@query`] - -== `@relationship` - -The `@relationship` directive is used to configure relationships between object types. - -Reference: xref::/type-definitions/types/relationships.adoc[Relationships], xref::schema-configuration/field-configuration.adoc#_relationship[`@relationship`] - -== `@relationshipProperties` - -Required to help you distinguish between interfaces which are used for relationship properties, and otherwise. - -Can only be used on interfaces, as per its definition: - -[source, graphql, indent=0] ----- -"""Required to differentiate between interfaces for relationship properties, and otherwise.""" -directive @relationshipProperties on INTERFACE ----- - -== `@relayId` - -The `@relayId` directive can be used on object type fields, to flag which field should be used as the global node identifier for Relay. Can be used once per type. The use of the `@relayId` directive ensures a unique node property constraint for the field. - -== `@selectable` - -The `@selectable` directive sets the availability of fields on queries and aggregations. - -Reference: xref:schema-configuration/field-configuration.adoc#_selectable[`@selectable`] - -== `@settable` - -The `@settable` directive sets the availability of fields on the create and update inputs. - -Reference: xref:schema-configuration/field-configuration.adoc#_settable[`@settable`] - -== `@subscription` - -This directive is used to limit Subscription events available in the library. - -Reference: xref:schema-configuration/type-configuration.adoc#_subscription[`@subscription`] - -== `@timestamp` - -The `@timestamp` directive flags fields to be used to store timestamps on create/update events. - -Reference: xref::/type-definitions/directives/autogeneration.adoc#type-definitions-autogeneration-timestamp[`@timestamp`] - -== `@unique` - -The `@unique` directive indicates that there should be a uniqueness constraint in the database for the fields that it is applied to. - -Reference: xref::/type-definitions/directives/indexes-and-constraints.adoc#type-definitions-constraints-unique[Unique node property constraints] diff --git a/modules/ROOT/pages/reference/directives/schema-configuration/type-configuration.adoc b/modules/ROOT/pages/reference/directives/schema-configuration/type-configuration.adoc deleted file mode 100644 index 0d0a5e46..00000000 --- a/modules/ROOT/pages/reference/directives/schema-configuration/type-configuration.adoc +++ /dev/null @@ -1,156 +0,0 @@ -[[schema-configuration-type-configuration]] -= Type Configuration - - -When representing a Neo4j Node, a GraphQL Object type produces multiple operation fields in the `Query`, `Mutation`, and `Subscription` types. -For example: - -[source, graphql, indent=0] ----- -type Movie { - title: String - length: Int -} ----- - -From these type definitions, the library generates the following operation fields: - -**Query**: - - * `movies` - * `moviesAggregate` - * `moviesConnection` - -**Mutation**: - - * `createMovies` - * `deleteMovies` - * `updateMovies` - -**Subscription**: - - * `movieCreated` - * `movieUpdated` - * `movieDeleted` -. - -This section explains how to reduce the operation fields produced using the directives `@query`, `@mutation`, and `@subscription`. - -== `@query` - -This directive is used to limit the availability of query operations in the library. - -=== Definition - -[source, graphql, indent=0] ----- -directive @query(read: Boolean! = true, aggregate: Boolean! = false) on OBJECT | SCHEMA ----- - -[NOTE] -==== -Aggregations will no longer be generated by default in the 4.0.0 version of the library. -See xref:migration/v4-migration/index.adoc#opt-in-aggregation[`Opt-in Aggregation`] for more information. -==== - -=== Usage - -==== Disable _movies_ and _moviesConnection_ operations - -[source, graphql, indent=0] ----- -type Movie @query(read: false, aggregate: true) { - title: String - length: Int -} ----- - -==== Disable _moviesAggregate_ operations - -[source, graphql, indent=0] ----- -type Movie @query(read: true, aggregate: false) { - title: String - length: Int -} ----- - -== `@mutation` - -This directive is used to limit the availability of Mutation operations in the library. - -=== Definition - -[source, graphql, indent=0] ----- -enum MutationFields { - CREATE - UPDATE - DELETE -} - -directive @mutation(operations: [MutationFields!]! = [CREATE, UPDATE, DELETE]) on OBJECT | SCHEMA ----- - -=== Usage - -==== Disable Create, Delete, and Update operations for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @mutation(operations: []) { - title: String - length: Int -} ----- - -==== Enable only Create operations for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @mutation(operations: [CREATE]) { - title: String - length: Int -} ----- - -== `@subscription` - -This directive is used to limit Subscription operations in the library. - -=== Definition - -[source, graphql, indent=0] ----- -enum SubscriptionEvent { - CREATED - UPDATED - DELETED - RELATIONSHIP_CREATED - RELATIONSHIP_DELETED -} - -directive @subscription(events: [SubscriptionEvent!]! = [CREATED, UPDATED, DELETED, RELATIONSHIP_CREATED, RELATIONSHIP_DELETED]) on OBJECT | SCHEMA ----- - -=== Usage - -==== Disable subscriptions for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @subscription(events: []) { - title: String - length: Int -} ----- - -==== Enable only _movieCreated_ subscription for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @subscription(events: [CREATED]) { - title: String - length: Int -} ----- diff --git a/modules/ROOT/pages/reference/type-definitions/interfaces.adoc b/modules/ROOT/pages/reference/type-definitions/interfaces.adoc deleted file mode 100644 index adc72a27..00000000 --- a/modules/ROOT/pages/reference/type-definitions/interfaces.adoc +++ /dev/null @@ -1,135 +0,0 @@ -[[schema-configuration-type-configuration]] -= Type Configuration - - -When representing a Neo4j Node, a GraphQL Object type produces multiple operation fields in the `Query`, `Mutation`, and `Subscription` types. -For example: - -[source, graphql, indent=0] ----- -type Movie { - title: String - length: Int -} ----- - -From these type definitions, the library generates the following operation fields: - -**Query**: - - * `movies` - * `moviesAggregate` - * `moviesConnection` - -**Mutation**: - - * `createMovies` - * `deleteMovies` - * `updateMovies` - -**Subscription**: - - * `movieCreated` - * `movieUpdated` - * `movieDeleted` -. - -This section explains how to reduce the operation fields produced using the directives `@query`, `@mutation`, and `@subscription`. - -== `@query` - -This directive is used to limit the availability of query operations in the library. - -=== Definition - -[source, graphql, indent=0] ----- -directive @query(read: Boolean! = true, aggregate: Boolean! = false) on OBJECT | SCHEMA ----- - -[NOTE] -==== -Aggregations will no longer be generated by default in the 4.0.0 version of the library. -See xref:migration/v4-migration/index.adoc#opt-in-aggregation[`Opt-in Aggregation`] for more information. -==== - -[[type-definitions-interfaced-types-querying]] -== Querying an interface - -Which implementations are returned by a query are dictated by the `where` filter applied. - -For example, the following will return all productions with title starting "The " for every actor: - -[source, graphql, indent=0] ----- -enum MutationFields { - CREATE - UPDATE - DELETE -} - -directive @mutation(operations: [MutationFields!]! = [CREATE, UPDATE, DELETE]) on OBJECT | SCHEMA ----- - -=== Usage - -==== Disable Create, Delete, and Update operations for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @mutation(operations: []) { - title: String - length: Int -} ----- - -==== Enable only Create operations for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @mutation(operations: [CREATE]) { - title: String - length: Int -} ----- - -== `@subscription` - -This directive is used to limit Subscription operations in the library. - -=== Definition - -[source, graphql, indent=0] ----- -enum SubscriptionFields { - CREATE - UPDATE - DELETE - CREATE_RELATIONSHIP - DELETE_RELATIONSHIP -} - -directive @subscription(operations: [SubscriptionFields!]! = [CREATE, UPDATE, DELETE, CREATE_RELATIONSHIP, DELETE_RELATIONSHIP]) on OBJECT | SCHEMA ----- - -=== Usage - -==== Disable subscriptions for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @subscription(operations: []) { - title: String - length: Int -} ----- - -==== Enable only _movieCreated_ subscription for _Movie_ - -[source, graphql, indent=0] ----- -type Movie @subscription(operations: [CREATE]) { - title: String - length: Int -} ----- \ No newline at end of file diff --git a/modules/ROOT/pages/subscriptions/events.adoc b/modules/ROOT/pages/subscriptions/events.adoc index 18dea0ac..644da9fb 100644 --- a/modules/ROOT/pages/subscriptions/events.adoc +++ b/modules/ROOT/pages/subscriptions/events.adoc @@ -10,9 +10,33 @@ This page covers a variety of subscription options offered by the Neo4j GraphQL [NOTE] === Only changes made through `@neo4j/graphql` should trigger the events here described. -Changes made directly to the database or using the xref::reference/directives/cypher.adoc[`@cypher` directive] will **not** trigger any event. -==== +Changes made directly to the database or using the xref::type-definitions/directives/cypher.adoc[`@cypher` directive] will **not** trigger any event. +=== + +== `CREATE` + +Subscriptions to `CREATE` events listen *only* to newly created nodes, not new relationships. +In this occasion, a new event is triggered for each new node, containing its properties. + +This action is performed with the top-level subscription `[type]Created`, which contains the following fields: + +* `event`: the event triggering this subscription (in this case, `CREATE`). +* `created`: top-level properties of the newly created node, without relationships. +* `timestamp`: the timestamp in which the mutation was made. +If a same query triggers multiple events, they should have the same timestamp. + +As an example, consider the following type definitions: + +[source,graphql,indent=0] +---- +type Movie { + title: String + genre: String +} +---- +Note, however, that only changes made through `@neo4j/graphql` should trigger events. +Changes made directly to the database or using the xref::type-definitions/directives/cypher.adoc[`@cypher` directive] will **not** trigger any event. == `CREATE` From 0ba28001ad2ec0bd3b69928903a7728bae31f785 Mon Sep 17 00:00:00 2001 From: Lidia Zuin <102308961+lidiazuin@users.noreply.github.com> Date: Thu, 5 Oct 2023 15:21:32 +0200 Subject: [PATCH 25/28] Update to the migrations guide (#43) * Update to the migrations guide * fixing conflicts * Apply suggestions from code review Co-authored-by: MacondoExpress * changes after review --------- Co-authored-by: MacondoExpress --- .../ROOT/pages/migration/authorization.adoc | 5 + modules/ROOT/pages/migration/index.adoc | 11 + modules/ROOT/pages/migration/ogm.adoc | 8 + .../ROOT/pages/migration/v2-migration.adoc | 606 ---------- .../ROOT/pages/migration/v3-migration.adoc | 253 ---- .../pages/migration/v4-migration/index.adoc | 1015 ----------------- 6 files changed, 24 insertions(+), 1874 deletions(-) delete mode 100644 modules/ROOT/pages/migration/v2-migration.adoc delete mode 100644 modules/ROOT/pages/migration/v3-migration.adoc delete mode 100644 modules/ROOT/pages/migration/v4-migration/index.adoc diff --git a/modules/ROOT/pages/migration/authorization.adoc b/modules/ROOT/pages/migration/authorization.adoc index 2ab125de..1d31119b 100644 --- a/modules/ROOT/pages/migration/authorization.adoc +++ b/modules/ROOT/pages/migration/authorization.adoc @@ -1,10 +1,15 @@ = Authentication and Authorization +<<<<<<< HEAD <<<<<<< HEAD:modules/ROOT/pages/migration/authorization.adoc :description: This page describes the changes in authentication and authorization features in version 4.0.0 of the Neo4j GraphQL Library. :page-aliases: auth/global-authentication.adoc, migration/v4-migration/authorization.adoc ======= :page-aliases: auth/global-authentication.adoc >>>>>>> e533fd8 (Add page-aliases for version 4 (#31) (#32)):modules/ROOT/pages/migration/v4-migration/authorization.adoc +======= +:description: This page describes the changes in authentication and authorization features in version 4.0.0 of the Neo4j GraphQL Library. +:page-aliases: auth/global-authentication.adoc, migration/v4-migration/authorization.adoc +>>>>>>> 166b72c (Update to the migrations guide (#43)) The largest breaking change in version 4.0.0 is the removal of the `@auth` directive, which requires a migration to the new `@authentication`, `@authorization`, and `@subscriptionsAuthorization` directives. diff --git a/modules/ROOT/pages/migration/index.adoc b/modules/ROOT/pages/migration/index.adoc index f4876bb9..4063f4a6 100644 --- a/modules/ROOT/pages/migration/index.adoc +++ b/modules/ROOT/pages/migration/index.adoc @@ -1,8 +1,12 @@ <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> 166b72c (Update to the migrations guide (#43)) [[v4-migration]] :description: This page lists the breaking changes from version 3.0.0 to 4.0.0 and describes how to update. :page-aliases: guides/v4-migration/index.adoc , migration/v4-migration/index.adoc = Migration to 4.0.0 +<<<<<<< HEAD ======= [[migration-guide]] = Migration from `neo4j-graphql-js` @@ -14,6 +18,13 @@ This page lists all breaking changes from the Neo4j GraphQL Library version 3.x == How to update +======= + +This page lists all breaking changes from the Neo4j GraphQL Library version 3.x to 4.x and how to update it. + +== How to update + +>>>>>>> 166b72c (Update to the migrations guide (#43)) To update your Neo4j GraphQL Library, use npm or the package manager of choice: [source, bash, indent=0] diff --git a/modules/ROOT/pages/migration/ogm.adoc b/modules/ROOT/pages/migration/ogm.adoc index 7b36a6b3..2f3852be 100644 --- a/modules/ROOT/pages/migration/ogm.adoc +++ b/modules/ROOT/pages/migration/ogm.adoc @@ -1,4 +1,5 @@ = OGM +<<<<<<< HEAD <<<<<<< HEAD:modules/ROOT/pages/migration/ogm.adoc :description: This page describes what updates were made to the OGM tool in version 4.0.0 of the Neo4j GraphQL Library. :page-aliases: guides/v4-migration/ogm.adoc, migration/v4-migration/ogm.adoc @@ -9,6 +10,13 @@ This page describes what updates were made to the OGM tool in version 4.0.0 of the Neo4j GraphQL Library. +======= +:description: This page describes what updates were made to the OGM tool in version 4.0.0 of the Neo4j GraphQL Library. +:page-aliases: guides/v4-migration/ogm.adoc, migration/v4-migration/ogm.adoc + +This page describes what updates were made to the OGM tool in version 4.0.0 of the Neo4j GraphQL Library. + +>>>>>>> 166b72c (Update to the migrations guide (#43)) == Database specification The method to specify the database that the OGM should use has been changed. diff --git a/modules/ROOT/pages/migration/v2-migration.adoc b/modules/ROOT/pages/migration/v2-migration.adoc deleted file mode 100644 index a6d93ec6..00000000 --- a/modules/ROOT/pages/migration/v2-migration.adoc +++ /dev/null @@ -1,606 +0,0 @@ -[[v2-migration]] -= 2.0.0 Migration -:page-aliases: guides/v2-migration/index.adoc, guides/v2-migration/miscellaneous.adoc, guides/v2-migration/unions.adoc, guides/v2-migration/mutations.adoc - - -Version 2.0.0 of `@neo4j/graphql` adds support for relationship properties, with some breaking changes to facilitate these new features. All of the required changes will be on the client side, and this guide will walk through what has changed. - -== How to Upgrade - -Simply update `@neo4j/graphql` using npm or your package manager of choice: - -[source, bash, indent=0] ----- -npm update @neo4j/graphql ----- - -From this point on, it is primarily Mutations which will form the bulk of the migration. - -[[v2-migration-mutations]] -== Mutations - -The most broadly affected area of functionality by the 2.0.0 upgrade are the nested operations of Mutations, to facilitate the mutation of and filtering on relationship properties. - -The examples in this section will be based off the following type definitions: - -[source, graphql, indent=0] ----- -type Actor { - name: String! - movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) -} - -type Movie { - title: String! - actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) -} ----- - -The theme that you will notice during this section is that as a general rule of thumb, a `node` field will need adding to your inputs where it will also be possible to filter on relationship properties. - -[[v2-migration-mutations-create]] -=== Create - -Focussing on the `createMovies` mutation, notice that the definition of the `createMovies` mutation is unchanged: - -[source, graphql, indent=0] ----- -input MovieCreateInput { - title: String! - actors: MovieActorsFieldInput -} - -type Mutation { - createMovies(input: [MovieCreateInput!]!): CreateMoviesMutationResponse! -} ----- - -There are no changes to any of the arguments or types at this level. However, within its nested operations, type modifications have taken place to allow for relationship properties. - -In practice, take a mutation that creates the film "The Dark Knight" and then: - -* Creates a new actor "Heath Ledger" -* Connects to the existing actor "Christian Bale" - -In the previous version of the library, this would have looked like this: - -[source, graphql, indent=0] ----- -mutation { - createMovies( - input: [ - { - title: "The Dark Knight" - actors: { - create: [ - { - name: "Heath Ledger" - } - ] - connect: [ - { - where: { - name: "Christian Bale" - } - } - ] - } - } - ] - ) { - movies { - title - } - } -} ----- - -This will now have to look like this in order to function in the same way: - -[source, graphql, indent=0] ----- -mutation { - createMovies( - input: [ - { - title: "The Dark Knight" - actors: { - create: [ - { - node: { - name: "Heath Ledger" - } - } - ] - connect: [ - { - where: { - node: { - name: "Christian Bale" - } - } - } - ] - } - } - ] - ) { - movies { - title - } - } -} ----- - -Note the additional level "node" before specifying the actor name for the create operation and in the connect where. This additional level allows for the setting of relationship properties for the new relationship, and filtering on existing relationship properties when looking for the node to connect to. See the page xref::mutations/index.adoc[mutations] for details on this. - -=== Update - -Focussing on the `updateMovies` mutation, notice that the definition of the `updateMovies` mutation is unchanged: - -[source, graphql, indent=0] ----- -type Mutation { - updateMovies( - where: MovieWhere - update: MovieUpdateInput - connect: MovieConnectInput - disconnect: MovieDisconnectInput - create: MovieRelationInput - delete: MovieDeleteInput - ): UpdateMoviesMutationResponse! -} ----- - -The `create` and `connect` nested operations are primarily the same as in the `createMovies` mutation, so please see the <> section for the difference for these operations. - -The `delete` nested operation is primarily the same as in the `deleteMovies` mutation, so please see the <> section for that. - -==== Update - -For example, say that you accidentally misspelt Christian Bale's surname and wanted to fix that. In the previous version, you might have achieved that by: - -[source, graphql, indent=0] ----- -mutation { - updateMovies( - where: { - title: "The Dark Knight" - } - update: { - actors: [ - { - where: { - name_ENDS_WITH: "Bail" - } - update: { - name: "Christian Bale" - } - } - ] - } - ) { - movies { - title - actors { - name - } - } - } -} ----- - -This will now have to look like this in order to function in the same way: - -[source, graphql, indent=0] ----- -mutation { - updateMovies( - where: { - title: "The Dark Knight" - } - update: { - actors: [ - { - where: { - node: { - name_ENDS_WITH: "Bail" - } - } - update: { - node: { - name: "Christian Bale" - } - } - } - ] - } - ) { - movies { - title - actors { - name - } - } - } -} ----- - -Note the added layer of abstraction of `node` in both the `where` and `update` clauses. - -==== Disconnect - -For example, say you mistakenly put Ben Affleck as playing the role of Batman in "The Dark Knight", and you wanted to disconnect those nodes. In the previous version, this would have looked like: - -[source, graphql, indent=0] ----- -mutation { - updateMovies( - where: { - title: "The Dark Knight" - } - disconnect: { - actors: [ - { - where: { - name: "Ben Affleck" - } - } - ] - } - ) { - movies { - title - actors { - name - } - } - } -} ----- - -This will now have to look like this in order to function in the same way: - -[source, graphql, indent=0] ----- -mutation { - updateMovies( - where: { - title: "The Dark Knight" - } - disconnect: { - actors: [ - { - where: { - node: { - name: "Ben Affleck" - } - } - } - ] - } - ) { - movies { - title - actors { - name - } - } - } -} ----- - -[[v2-migration-mutations-delete]] -=== Delete - -Focussing on the `deleteMovies` mutation, notice that the definition of the `deleteMovies` mutation is unchanged: - -[source, graphql, indent=0] ----- -input MovieDeleteInput { - actors: [MovieActorsDeleteFieldInput!] -} - -type Mutation { - deleteMovies(where: MovieWhere, delete: MovieDeleteInput): DeleteInfo! -} ----- - -There are no changes to any of the arguments or types at this level, but there are some details to note in the `MovieActorsDeleteFieldInput` type. - -Previously, you would have expected this to look like: - -[source, graphql, indent=0] ----- -input MovieActorsDeleteFieldInput { - delete: ActorDeleteInput - where: ActorWhere -} ----- - -This allowed you to filter on fields of the `Actor` type and delete based on that. However, following this upgrade, you will find: - -[source, graphql, indent=0] ----- -input MovieActorsDeleteFieldInput { - delete: ActorDeleteInput - where: MovieActorsConnectionWhere -} ----- - -This means that not only can you filter on node properties, but also relationship properties, in order to find and delete `Actor` nodes. - -In practice, a mutation that deletes the film "The Dark Knight" and the related actor "Christian Bale" would have previously looked like this: - -[source, graphql, indent=0] ----- -mutation { - deleteMovies( - where: { - title: "The Dark Knight" - } - delete: { - actors: { - where: { - name: "Christian Bale" - } - } - } - ) { - nodesDeleted - relationshipsDeleted - } -} ----- - -This will now have to look like this in order to function in the same way: - -[source, graphql, indent=0] ----- -mutation { - deleteMovies( - where: { - title: "The Dark Knight" - } - delete: { - actors: { - where: { - node: { - name: "Christian Bale" - } - } - } - } - ) { - nodesDeleted - relationshipsDeleted - } -} ----- - -Note the additional level "node" before specifying the actor name. - -[[v2-migration-unions]] -== Unions - -In this release, the decision was made to take the opportunity to overhaul the existing support for unions on relationship fields, laying down the foundations for adding top-level union support in the future. - -All examples in this section will be based off the following type definitions: - -[source, graphql, indent=0] ----- -type Actor { - name: String! - actedIn: [Production!]! @relationship(type: "ACTED_IN", direction: OUT) -} - -type Movie { - title: String! - actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) -} - -type Series { - title: String! - actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) -} - -union Production = Movie | Series ----- - -=== Input types - -The structure of input types for union queries and mutations have been changed for user friendliness, and a more consistent API. - -Essentially, field names which were previously of template `_` (for example, "actedIn_Movie") are now an object, with the field name at the top, and the member types under it. - -For example, a Mutation which would have previously been: - -[source, graphql, indent=0] ----- -mutation { - createActors( - input: [ - { - name: "Tom Hiddleston" - actedIn_Movie: { - create: [ - { - title: "The Avengers" - } - ] - } - actedIn_Series: { - create: [ - { - title: "Loki" - } - ] - } - } - ] - ) -} ----- - -Will now be: - -[source, graphql, indent=0] ----- -mutation { - createActors( - input: [ - { - name: "Tom Hiddleston" - actedIn: { - Movie: { - create: [ - { - node: { - title: "The Avengers" - } - } - ] - } - Series: { - create: [ - { - node: { - title: "Loki" - } - } - ] - } - } - } - ] - ) -} ----- - -Note the change in structure for union input, but also the additional `node` level which enables the use of relationship properties. These changes are consistent across all operations, including `where`. - -=== Filtering union fields - -There has been a slight change to how you filter union fields, adding a `where` level above each union member. For example, for a query which would have used to have looked like: - -[source, graphql, indent=0] ----- -query { - actors { - name - actedIn(Movie: { "The Avengers" }) { - ... on Movie { - title - } - } - } -} ----- - -This will now be written like: - -[source, graphql, indent=0] ----- -query { - actors { - name - actedIn(where: { Movie: { "The Avengers" }}) { - ... on Movie { - title - } - } - } -} ----- - -Furthermore, the where argument used now dictates which union members are returned from the database, to prevent overfetching. Please see xref::troubleshooting.adoc#appendix-preventing-overfetching[this page] for background and explanation of this decision. - -[[v2-migration-miscellaneous]] -== Miscellaneous - -=== `skip` renamed to `offset` - -In the release of Apollo Client 3.0, it became a bit more opinionated about pagination, favouring `offset` and `limit` over `skip` and `limit`. Acknowledging that the majority of users will be using Apollo Client 3.0, the page-based pagination arguments have been updated to align with this change. - -For example, fetching page 3 of pages of 10 movies would have looked like the following in version `1.x`: - -[source, graphql, indent=0] ----- -query { - movies(options: { skip: 20, limit: 10 }) { - title - } -} ----- - -This will now need to queried as follows: - -[source, graphql, indent=0] ----- -query { - movies(options: { offset: 20, limit: 10 }) { - title - } -} ----- - -=== Count queries - -Whilst not a necessary migration step, if you are using page-based pagination, it's important to note the addition of count queries in version 2.0.0. These will allow you to calculate the total number of pages for a particular filter, allowing you to implement much more effective pagination. - -== Schema validation - -In version 2.0.0, there are greater levels of schema validation. However, upon upgrading, you might find that validation is too strict (for example if using certain generated types in your definitions). You can temporarily disable this new validation on construction: - -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - config: { - skipValidateTypeDefs: true, - }, -}) ----- - -If you need to do this, please report the scenario as an issue on GitHub. - -=== `_IN` and `_NOT_IN` filters on relationships removed - -There were previously `_IN` and `_NOT_IN` filters for one-to-many and one-to-one relationships, but these were surplus to requirements, and didn't match for all cardinalities (many-to-many relationships don't have `_INCLUDES` and `_NOT_INCLUDES`). These may be added back in the future if and when we look more holistically at distinguishing between different relationship cardinalities. - -You can still achieve identical filters through different routes. For example, if you had the following schema: - -[source, graphql, indent=0] ----- -type Movie { - title: String! - director: Director! @relationship(type: "DIRECTED", direction: IN) -} - -type Director { - name: String! - movies: [Movie!]! @relationship(type: "DIRECTED", direction: OUT) -} ----- - -You would have been able to run the following query: - -[source, graphql, indent=0] ----- -query { - movies(where: { director_IN: [{ name: "A" }, { name: "B" }] }) { - title - } -} ----- - -You can still achieve exactly the same filter with the following: - -[source, graphql, indent=0] ----- -query { - movies(where: { director: { OR: [{ name: "A" }, { name: "B" }]} }) { - title - } -} ----- diff --git a/modules/ROOT/pages/migration/v3-migration.adoc b/modules/ROOT/pages/migration/v3-migration.adoc deleted file mode 100644 index d9657ba9..00000000 --- a/modules/ROOT/pages/migration/v3-migration.adoc +++ /dev/null @@ -1,253 +0,0 @@ -[[v3-migration]] -= 3.0.0 Migration -:page-aliases: guides/v3-migration/index.adoc - - -This document lists all breaking changes from version 2.x.y to 3.0.0 and how to update. - -== How to upgrade -Simply update `@neo4j/graphql` using npm or your package manager of choice: - -[source, bash, indent=0] ----- -npm update @neo4j/graphql ----- - -== Asynchronous schema generation -Schema generation is now asynchronous. Instead of using the property `schema`, now the method `getSchema` will return the schema -as a `Promise`. This means that creating a server now requires awaiting for that method: - -Instead of -[source, JavaScript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); -const server = new ApolloServer({ - schema: neoSchema.schema, -}); ----- - -Now you'll need to do the following: - -[source, JavaScript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); -neoSchema.getSchema().then((schema) => { - const server = new ApolloServer({ - schema: schema - }); -}); ----- - -== Relationship changes -This release contains an overhaul of our relationship validations, which will require a few changes to the schema. - -=== Many-to-* relationships -To improve consistency and validation, **all** "many-to-*" relationships need to be defined as _required_ in the schema: - -[source, graphql, indent=0] ----- -type Movie { - actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) - director: Director @relationship(type: "DIRECTED", direction: IN) -} ----- - -Note that any other notation, such as `[Actor]` or `[Actor!]` will **not** be valid. "One-to-one" relationships -such as `Director` remain unchanged. - -=== Relationship cardinality -Runtime checks for "one-to*" relationships have been added in this release, ensuring that the correct number of relationships exist. This means that some -databases with inconsistent relationships between the schema definition and the actual data may now fail in some queries. -This may have happened due to different reasons such as direct changes in the database or changes to the type definitions. -Previous versions of `@neo4j/graphql` did not have any consistency check, so normal use of these versions may have lead to -inconsistent relationships. - -For these cases, please ensure that the database is following your schema definition or update the schema to reflect the -actual existing relationships, taking care of which relationships are 1-to-* or many-to-many. - -== Count query no longer supported -Queries using `count` at the root level are no longer supported. For example: -[source, graphql, indent=0] ----- -query { - usersCount -} ----- - -The same operation, can now be achieved with a xref::queries-aggregations/queries.adoc#_counting_using_aggregation[count aggregation] query: - -[source, graphql, indent=0] ----- -query { - usersAggregate { - count - } -} ----- - -=== Relationship filters -`where` filters for relationship queries now explicitly state `ALL`, `NONE`, `SINGLE`, and `SOME` as part of filter name. - -Queries using old relationship filters, will now need to use `\{relationship\}_SOME`. For example: - -[source, graphql, indent=0] ----- -query { - movies(where: { - actors: { - name: "John" - } - }) { - title - } -} ----- - -Should be: - -[source, graphql, indent=0] ----- -query { - movies(where: { - actors_SOME: { - name: "John" - } - }) { - title - } -} ----- - -And, instead of `_NOT`, `_NONE` should be used. - -NOTE: Old queries will still work in this release, but are marked as `@deprecated` and will not be available in the future. - -== `@ignore` directive renamed to `@computed` -To better reflect its intended usage, the `@ignore` directive is now named `@computed`. Behaviour is unchanged, so you just need to -rename this directive in your schema. - -== Auth plugin system -label:deprecated[] - -Auth setup now relies on _plugins_ to setup the configuration. -You'll need to install `@neo4j/graphql-plugin-auth` or a custom plugin. - -=== JWT auth -For JWT authorization, instead of the previous configuration: -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - config: { - jwt: { - secret - } - } -}); ----- - -Now the configuration should be passed through `Neo4jGraphQLAuthJWTPlugin`: - -[source, javascript, indent=0] ----- -import { Neo4jGraphQL } from "@neo4j/graphql"; -import { Neo4jGraphQLAuthJWTPlugin } from "@neo4j/graphql-plugin-auth"; - -const neoSchema = new Neo4jGraphQL({ - typeDefs, - plugins: { - auth: new Neo4jGraphQLAuthJWTPlugin({ - secret: "super-secret" - }) - } -}); ----- - - -=== JWKS decoding - -https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets[JSON Web Key Sets] are now supported through `Neo4jGraphQLAuthJWKSPlugin`. - -Instead of setting the endpoint directly: -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - config: { - jwt: { - jwksEndpoint: "https://YOUR_DOMAIN/.well-known/jwks.json" - } - } -}); ----- - -Now the `Neo4jGraphQLAuthJWKSPlugin` would take care of that: -[source, javascript, indent=0] ----- -import { Neo4jGraphQL } from "@neo4j/graphql"; -import { Neo4jGraphQLAuthJWKSPlugin } from "@neo4j/graphql-plugin-auth"; - -const neoSchema = new Neo4jGraphQL({ - typeDefs, - plugins: { - auth: new Neo4jGraphQLAuthJWKSPlugin({ - jwksEndpoint: "https://YOUR_DOMAIN/well-known/jwks.json", - }) - } -}); ----- - -NOTE: Please, refer to xref::authentication-and-authorization/index.adoc[auth setup] before setting up auth. - -== Types plurals changes -To improve consistency, some automatically generated plurals (e.g. `createActors`) have changed. This may cause issues if -your types use conventions such as `snake_case`. - -Because of this, you may find generated queries and mutations may have different names. If you encounter this problem, -please update your clients to use the new query names or use the `plural` option in the xref::/type-definitions/directives/database-mapping.adoc#type-definitions-node[@node directive] -to force a custom plural value. - -== Custom Directives -Defining and applying custom directives has changed significantly, if you are using or plan to use custom directives, make -sure to check the up-to-date documentation on xref::/type-definitions/directives/custom-directives.adoc[custom directives]. - -== Types changes -Some automatically generated types have changed to improve consistency. -These should not require any changes from most developers, unless types names are directly used. - -Some automatically generated types have changed to improve consistency. -These should not require any changes from the developer in most cases, unless in cases where types names are directly used. - -=== Removal of nested operation fields for `connectOrCreate` -Input types for `onCreate` in `connectOrCreate` operations no longer accept relationship fields. They were originally added in error and did not function as one would expect, so there is no regression in functionality. - -=== Non Nullable Aggregation Results -Aggregation results may now be non-nullable for required fields, yielding more accurate types. - -For example, for the following types: -[source, graphql, indent=0] ----- -type User { - name: String! - lastName: String -} ----- - -Will yield different types for aggregations over `name` and `lastName`: -[source, graphql, indent=0] ----- -type UserAggregateSelection { - count: Int! - name: StringAggregateSelectionNonNullable! - lastName: StringAggregateSelectionNullable! -} ----- - -=== ConnectionWhere types renamed -`ConnectionWhere` types renamed to improve consistency with other similarly named types. - -== Neo4j support -Neo4j 4.1 is no longer supported in 3.0.0, inline with the https://neo4j.com/developer/kb/neo4j-supported-versions/[supported versions list]. - -== GraphQL support -`graphql@^15.0.0` is no longer supported, please upgrade to `graphql@^16.0.0` using `npm` or the package manager of your choice. diff --git a/modules/ROOT/pages/migration/v4-migration/index.adoc b/modules/ROOT/pages/migration/v4-migration/index.adoc deleted file mode 100644 index 6375ab27..00000000 --- a/modules/ROOT/pages/migration/v4-migration/index.adoc +++ /dev/null @@ -1,1015 +0,0 @@ -[[v4-migration]] -= 4.0.0 Migration -:page-aliases: guides/v4-migration/index.adoc - - -This document lists all breaking changes from version 3.x.y to 4.0.0 and how to update. - -== How to upgrade -Simply update `@neo4j/graphql` using npm or your package manager of choice: - -[source, bash, indent=0] ----- -npm update @neo4j/graphql ----- - -== Constructor arguments - -If you were passing any arguments from https://the-guild.dev/graphql/tools/docs/api/interfaces/schema_src.iexecutableschemadefinition[`IExecutableSchemaDefinition`] into the library other than `typeDefs` and `resolvers`, these are no longer supported. - -=== Removal of `config` - -==== `debug` - -The programmatic toggle for debug logging has been moved from `config.enableDebug` to simply `debug`. - -An example of `enableDebug`: - -[source, javascript, indent=0] ----- -const { Neo4jGraphQL } = require("@neo4j/graphql"); -const neo4j = require("neo4j-driver"); -const { ApolloServer } = require("apollo-server"); - -const typeDefs = ` - type Movie { - title: String! - } -`; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("neo4j", "password") -); - -const neoSchema = new Neo4jGraphQL({ - typeDefs, - driver, - config: { - enableDebug: true, - } -}); ----- - -This now becomes: - -[source, javascript, indent=0] ----- -const { Neo4jGraphQL } = require("@neo4j/graphql"); -const neo4j = require("neo4j-driver"); -const { ApolloServer } = require("apollo-server"); - -const typeDefs = ` - type Movie { - title: String! - } -`; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("neo4j", "password") -); - -const neoSchema = new Neo4jGraphQL({ - typeDefs, - driver, - debug: true, -}); ----- - -==== `driverConfig` moved to context - -Session configuration is now available only in the context under the `sessionConfig` key. - -This was previously `driverConfig`, available in both the constructor and in the context: - -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - config: { - driverConfig: { - database: "different-db" - }, - }, -}) ----- - -The new `sessionConfig` key is only available in the context: - -[source, javascript, indent=0] ----- -import { ApolloServer } from '@apollo/server'; -import { startStandaloneServer } from '@apollo/server/standalone'; -import { Neo4jGraphQL } from "@neo4j/graphql"; -import neo4j from "neo4j-driver"; - -const typeDefs = `#graphql - type User { - name: String - } -`; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("neo4j", "password") -); - -const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); - -const server = new ApolloServer({ - schema: await neoSchema.getSchema(), -}); - -await startStandaloneServer(server, { - context: async ({ req }) => ({ sessionConfig: { database: "my-database" }}), -}); ----- - -The `bookmarks` key has been removed because it is no longer needed with the bookmark manager of the newer driver. - -==== `enableRegex` replaced by `MATCHES` in features.filters - -`config.enableRegex` has been replaced by `MATCHES` in features.filters. With this change comes more granularity in the feature configuration. You can now enable the `MATCHES` filter on `String` and `ID` fields separately. - -A direct replacement of the `enableRegex: true` configuration would be as follows: - -[source, javascript, indent=0] ----- -neoSchema = new Neo4jGraphQL({ - typeDefs, - features: { - filters: { - String: { - MATCHES: true, - }, - ID: { - MATCHES: true, - }, - }, - }, -}); ----- - -==== `queryOptions` moved to the context - -If you had a need to pass in Cypher query options for query tuning, this interface has been changed. - -The config option `queryOptions` has now become `cypherQueryOptions` inside the context function, and it now accepts simple strings instead of enums. - -The following is an example before the change: - -[source, javascript, indent=0] ----- -const { Neo4jGraphQL, CypherRuntime } = require("@neo4j/graphql"); -const { ApolloServer } = require("apollo-server"); - -const typeDefs = ` - type Movie { - title: String! - } -`; - -const neoSchema = new Neo4jGraphQL({ - typeDefs, - config: { - queryOptions: { - runtime: CypherRuntime.INTERPRETED, - }, - }, -}); ----- - -This is what is required after the change: - -[source, javascript, indent=0] ----- -const { Neo4jGraphQL } = require("@neo4j/graphql"); -const { ApolloServer } = require("apollo-server"); - -const typeDefs = ` - type Movie { - title: String! - } -`; - -const neoSchema = new Neo4jGraphQL({ - typeDefs, -}); - -const server = new ApolloServer({ - schema: await neoSchema.getSchema(), -}); - -await startStandaloneServer(server, { - context: async ({ req }) => ({ cypherQueryOptions: { runtime: "interpreted" }}), -}); ----- - -This reflects the fact that the Cypher query options are set on a per-request basis. - -[[startup-validation]] -==== `skipValidateTypeDefs` - -The argument `skipValidateTypeDefs` has been moved to the top-level of the constructor input and renamed `validate`, which defaults to `true`. - -To disable type definition validation, the following config option should be used: - -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - validate: false, -}) ----- - -If you started using the `config.startupValidation` option, this has also been rolled into the same `validate` setting for simplicity. -The `resolvers` option of this is now just a warning, and `noDuplicateRelationshipFields` is now a mandatory check rolled into `validate`. - -[subscriptions-options] -=== Subscription options - -Subscriptions are no longer configured as a plugin, but as a feature within the `features` option. - -This means that, instead of: - -[source, javascript] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - plugins: { - subscriptions: plugin, - }, -}); ----- - -Subscriptions are now defined as: - -[source, javascript] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - features: { - subscriptions: plugin, - }, -}); ----- - -==== Default subscriptions - -The class `Neo4jGraphQLSubscriptionsSingleInstancePlugin` is no longer exported. -Instead, the default subscriptions behavior can be enabled by setting the `subscriptions` option to `true` . - -Instead of: - -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - plugin: { - subscriptions: new Neo4jGraphQLSubscriptionsSingleInstancePlugin(), - }, -}); ----- - -The default subscriptions can be enabled with: - -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - features: { - subscriptions: true - }, -}); ----- - -==== Neo4j GraphQL Subscriptions AMQP package - -The name of the interface underlying the Subscriptions system has changed from `Neo4jGraphQLSubscriptionsPlugin` to `Neo4jGraphQLSubscriptionsEngine`. -If you were previously using the `@neo4j/graphql-plugins-subscriptions-amqp` package, this has been changed to `@neo4j/graphql-amqp-subscriptions-engine` to reflect this underlying change. - -Please uninstall the previous package and install the new one: - -[source, bash, indent=0] ----- -npm uninstall @neo4j/graphql-plugins-subscriptions-amqp -npm install @neo4j/graphql-amqp-subscriptions-engine ----- - -Please then update any imports from: - -[source, javascript, indent=0] ----- -import { Neo4jGraphQLSubscriptionsAMQPPlugin } from "@neo4j/graphql-plugins-subscriptions-amqp"; ----- - -To: - -[source, javascript, indent=0] ----- -import { Neo4jGraphQLAMQPSubscriptionsEngine } from "@neo4j/graphql-amqp-subscriptions-engine"; ----- - -Then change the instantiations from: - -[source, javascript, indent=0] ----- -const plugin = new Neo4jGraphQLSubscriptionsAMQPPlugin({ - connection: { - hostname: "localhost", - username: "guest", - password: "guest", - }, -}); ----- - -To: - -[source, javascript, indent=0] ----- -const subscriptionsEngine = new Neo4jGraphQLAMQPSubscriptionsEngine({ - connection: { - hostname: "localhost", - username: "guest", - password: "guest", - }, -}); ----- - -==== Custom Subscription Plugins - -The underlying subscription system has not changed. -Custom behavior can be implemented the same way, by creating a class implementing the interface described in xref::subscriptions/engines.adoc#custom-subscription[Subscriptions Engines]. - -However, if using TypeScript, the exported interface to implement these classes has been renamed from `Neo4jGraphQLSubscriptionsPlugin` to `Neo4jGraphQLSubscriptionsEngine`. - -== Updated Directives - -We have renamed a number of directives and their arguments, in order to make using `@neo4j/graphql` more intuitive. - -=== `@alias` values are now automatically escaped - -Properties in the alias directive automatically escaped using backticks. If you were using backticks in the `property` argument of your `@alias` directives, you should now remove the escape strings as this is covered by the library. - -[source, graphql, indent=0] ----- -type User { - id: ID! @id - username: String! @alias(property: "dbUserName") -} ----- - -[populatedBy-migration] -=== `@callback` renamed to `@populatedBy` - -Previously, there was ambiguity over the behaviour of `@callback`. As the directive is used to populate a value on input, it has been renamed `@populatedBy` to reflect this. -Additionally, the `name` argument was previously used to specify the callback used to populate the field's value. -This has been renamed to `callback` to make it clear that it refers to a callback. - -Therefore, the following usage of the directive would be invalid: - -[source, graphql, indent=0] ----- -type User { - id: ID! @callback(name: "nanoid", operations: [CREATE]) - firstName: String! - surname: String! -} ----- - -It would instead need to be updated to use the new directive and argument as below: - -[source, graphql, indent=0] ----- -type User { - id: ID! @populatedBy(callback: "nanoid", operations: [CREATE]) - firstName: String! - surname: String! -} ----- - -Configuration for callbacks has also been moved as part of this change. Before these changes, a callback named `nanoid` would need to be defined as below: - -[source, javascript, indent=0] ----- -new Neo4jGraphQL({ - typeDefs, - config: { - callbacks: { - nanoid: () => { return nanoid(); } - } - } -}); ----- - -This has been changed to use the `features` constructor object: - -[source, javascript, indent=0] ----- -new Neo4jGraphQL({ - typeDefs, - features: { - populatedBy: { - callbacks: { - nanoid: () => { return nanoid(); } - } - } - } -}); ----- - -[customResolver-migration] -=== `@computed` renamed to `@customResolver` - -Previously, there was ambiguity over the behaviour of `@computed` and it wasn't clear that it was intended to be used with a custom resolver. In order to make this clear, `@computed` has been renamed to `@customResolver`. -Furthermore, the behaviour of the `from` argument was not clear. The argument is used to specify which fields other fields are required by the custom resolver. As a result, `from` has been renamed to `requires`. - -These changes mean that the following type definition is invalid in version 4.0.0: - -[source, graphql, indent=0] ----- -type User { - firstName: String! - lastName: String! - fullName: String! @computed(from: ["firstName", "lastName"]) -} ----- - -Instead, it would need to be updated to use the new directive and argument as below: - -[source, graphql, indent=0] ----- -type User { - firstName: String! - lastName: String! - fullName: String! @customResolver(requires: ["firstName", "lastName"]) -} ----- - -Note that before and after these changes, a custom resolver would need to be defined as below: - -[source, javascript, indent=0] ----- -new Neo4jGraphQL({ - typeDefs, - resolvers: { - User: { - fullName: ({ firstName, lastName }, args, context, info) => (`${firstName} ${lastName}`), - } - } -}); ----- - -==== `requires` changes - -In version 4.0.0, it is now possible to require non-scalar fields. This means it is also possible to require fields on related type. -To make this possible, the `requires` argument now accept a graphql selection set instead of a list of strings. - -Therefore, the following type definitions: - -[source, graphql, indent=0] ----- -type User { - firstName: String! - lastName: String! - fullName: String! @customResolver(requires: ["firstName", "lastName"]) -} ----- - -Would need to be modified to use a selection set as below: - -[source, graphql, indent=0] ----- -type User { - firstName: String! - lastName: String! - fullName: String! @customResolver(requires: "firstName lastName") -} ----- - -Below is a more advanced example showing what the selection set is capable of: - -[source, graphql, indent=0] ----- -interface Publication { - publicationYear: Int! -} - -type Author { - name: String! - publications: [Publication!]! @relationship(type: "WROTE", direction: OUT) - publicationsWithAuthor: [String!]! - @customResolver( - requires: "name publications { publicationYear ...on Book { title } ... on Journal { subject } }" - ) -} - -type Book implements Publication { - title: String! - publicationYear: Int! - author: [Author!]! @relationship(type: "WROTE", direction: IN) -} - -type Journal implements Publication { - subject: String! - publicationYear: Int! - author: [Author!]! @relationship(type: "WROTE", direction: IN) -} ----- - -Additionally, the requires argument also validates the required selection set against your type definitions. -Therefore, as there is no field called `someFieldThatDoesNotExist`, an error would be thrown on startup if you tried to use the following type definitions: - -[source, graphql, indent=0] ----- -type User { - firstName: String! - lastName: String! - fullName: String! @customResolver(requires: "firstName someFieldThatDoesNotExist") -} ----- - -=== `@cypher` changes -The default behaviour of the `@cypher` directive regarding the translation will change: Instead of using https://neo4j.com/labs/apoc/4.0/overview/apoc.cypher/apoc.cypher.runFirstColumnMany/[apoc.cypher.runFirstColumnMany] it will directly wrap the query within a `CALL { }` subquery. This behvaiour has proven to be much more performant for the same queries, however, it may lead to unexpected changes, mainly when using Neo4j 5.x, where the subqueries need to be _aliased_. - -On top of that, to improve performance, it is recommended to pass the returned alias in the property `columnName`, to ensure the subquery is properly integrated into the larger query. - -For example: - -The graphql query: -[source, graphql, indent=0] ----- -type query { - test: String! @cypher(statement: "RETURN 'hello'") -} ----- - -Would get translated to: -[source,cypher, indent=0] ----- -CALL { - RETURN 'hello' -} -WITH 'hello' AS this -RETURN this ----- - -Which is invalid in Neo4j 5.x. - -To fix it we just need to ensure the `RETURN` elements are aliased: -[source, graphql, indent=0] ----- -type query { - test: String! @cypher(statement: "RETURN 'hello' as result") -} ----- - -This will be a breaking change, but this new behaviour can be used, as an experimental option with the `columnName` flag in the `@cypher` directive: - -[source, graphql, indent=0] ----- -type query { - test: String! @cypher(statement: "RETURN 'hello' as result", columnName: "result") -} ----- - -Additionally, escaping strings is no longer needed. - -=== `@exclude` removed - -The `@exclude` directive has been removed in favor of much more granular configuration directives. - -The new `@query`, `@mutation` and `@subscription` directives instead allow for fully granular configuration for each operation. - -As a direct migration, the following usages are equivalent: - -* `@exclude` and `@query(read: false, aggregate: false) @mutation(operations: []) @subscription(events: [])`. -* `@exclude(operations: [READ])` and `@query(read: false, aggregate: false)`. -* `@exclude(operation: [CREATE, UPDATE, DELETE])` and `@mutation(operations: [])`. - -Whilst there is more verbosity, the directives are significantly more powerful and extensible as the library gains features. - -[full-text-migration] -=== `@fulltext` changes - -In version 4.0.0, a number of improvements have been made to full-text queries. These include the ability to return the full-text score, filter by the score and sorting by the score. - -However, these improvements required a number of breaking changes. - -==== Query changes - -Full-text queries now need to be performed using a top-level query, instead of being performed using an argument on a node query. - -As a result, the following query is now invalid: - -[source, graphql, indent=0] ----- -query { - movies(fulltext: { movieTitleIndex: { phrase: "Some Title" } }) { - title - } -} ----- - -The new top-level queries can be used to return the full-text score, which indicates the confidence of a match, as well as the nodes that have been matched. - -.The new top-level queries accept the following arguments: -* `phrase` which specifies the string to search for in the full-text index. -* `where` which accepts a min/max score as well as the normal filters available on a node. -* `sort` which can be used to sort using the score and node attributes. -* `limit` which is used to limit the number of results to the given integer. -* `offset` which is used to offset by the given number of results. - -The new top-level queries means that for the following type definition: - -[source, graphql, indent=0] ----- -type Movie @fulltext(indexes: [{ indexName: "MovieTitle", fields: ["title"] }]) { # Note that indexName is the new name for the name argument. More about this below. - title: String! -} ----- - -The following top-level query and type definitions would be generated by the library: - -[source, graphql, indent=0] ----- -type Query { - movieFulltextMovieTitle(phrase: String!, where: MovieFulltextWhere, sort: [MovieFulltextSort!], limit: Int, offset: Int): [MovieFulltextResult!]! -} - -"""The result of a fulltext search on an index of Movie""" -type MovieFulltextResult { - score: Float - movies: Movie -} - -"""The input for filtering a fulltext query on an index of Movie""" -input MovieFulltextWhere { - score: FloatWhere - movie: MovieWhere -} - -"""The input for sorting a fulltext query on an index of Movie""" -input MovieFulltextSort { - score: SortDirection - movie: MovieSort -} - -"""The input for filtering the score of a fulltext search""" -input FloatWhere { - min: Float - max: Float -} ----- - -This query can be used to perform a full-text query as below: - -[source, graphql, indent=0] ----- -query { - movieFulltextMovieTitle( - phrase: "Full Metal Jacket", - where: { score: min: 0.4 }, - sort: [{ movie: { title: ASC } }], - limit: 5, - offset: 10 - ) { - score - movies { - title - } - } -} ----- - -The above query would be expected to return results in the following format: - -[source, json, indent=0] ----- -{ - "data": { - "movieFulltextMovieTitle": [ - { - "score": 0.44524085521698, - "movie": { - "title": "Full Moon High" - } - }, - { - "score": 1.411118507385254, - "movie": { - "title": "Full Metal Jacket" - } - } - ] - } -} ----- - -==== Argument changes - -.The following changes have been made to `@fulltext` arguments: -* `queryName` has been added to specify a custom name for the top-level query that is generated. -* `name` has been renamed to `indexName` to avoid ambiguity with the new `queryName` argument. - -These changes means that the following type definition is now invalid: - -[source, graphql, indent=0] ----- -type Movie @fulltext(indexes: [{ name: "MovieTitle", fields: ["title"] }]) { - title: String! -} ----- - -The `name` argument would need to be replaced with `indexName` as below: - -[source, graphql, indent=0] ----- -type Movie @fulltext(indexes: [{ indexName: "MovieTitle", fields: ["title"] }]) { - title: String! -} ----- - -The `queryName` argument can be used as below: - -[source, graphql, indent=0] ----- -type Movie @fulltext(indexes: [{ queryName: "moviesByTitle", indexName: "MovieTitle", fields: ["title"] }]) { - title: String! -} ----- - -This means the top-level query would now be `moviesByTitle` instead of `movieFulltextMovieTitle`: - -[source, graphql, indent=0] ----- -type Query { - moviesByTitle(phrase: String!, where: MovieFulltextWhere, sort: [MovieFulltextSort!], limit: Int, offset: Int): [MovieFulltextResult!]! -} ----- - -=== `@id` changes - -The `@id` directive has been completely pared back in version 4.0.0, with _all_ of its arguments removed. -This has been done to reduce the number of features that this directive was used to toggle, and to ensure that its behaviour is consistent no matter where it is used. - -==== `autogenerate` - -The default value of `autogenerate` was `true`. If this was set to `false`, the `@id` directive was almost a no-op only used to manage a unique node property constraint. Use the `@unique` directive instead. - -==== `global` - -The `global` argument was used to configure the field that would form the global node identifier for Relay. - -This functionality has been moved into its own directive, `@relayId`. The use of `@relayId` will ensure a unique node property constraint for the field. - -==== `unique` - -The `@id` directive used to also manage unique node property constraints for a field. This functionality has now been removed, use the `@unique` directive in combination with `@id` if you want the field to be backed by a constraint. - -=== `@node` changes - -[plural-migration] -==== `plural` argument removed from `@node` and replaced with `@plural` - -How a type name is pluralised has nothing to do with nodes in the database. As a result, having a `plural` argument on the `@node` directive did not make sense. -As a result, the `plural` argument of `@node` has been removed and replaced with a new `@plural` directive. The `@plural` directive takes the pluralised type name using the `value` argument. - -This means that the following type definition is invalid: - -[source, graphql, indent=0] ----- -type Tech @node(label: "TechDB", plural: "Techs") { - name: String -} ----- - -It would need to be updated to use the new directive as below: - -[source, graphql, indent=0] ----- -type Tech @node(label: "TechDB") @plural(value: "Techs") { - name: String -} ----- - -[label-migration] -==== `label` and `additionalLabels` arguments removed from `@node` and replaced with new argument `labels` - -There is no concept of a "main label" in the Neo4j database. As such, keeping these two separate arguments causes a disconnect between the database and the GraphQL library. -As a result, the `label` and `additionalLabels` arguments have been condensed into a single argument `labels` which will accept a list of string labels that used when a node of the given GraphQL type is created. -Please note that defining `labels` means you take control of the database labels of the node. Indexes and constraints in Neo4j only support a single label, for which the first element of the `labels` argument will be used. - -The equivalent of using just the `label` argument is now a list with a single value: - -[source, graphql, indent=0] ----- -type Tech @node(label: "TechDB") { - name: String -} -# becomes -type Tech @node(labels: ["TechDB"]) { - name: String -} ----- - -When creating the equivalent of using just the `additionalLabels` argument now requires the first value in the list to be the GraphQL type name: - -[source, graphql, indent=0] ----- -type Tech @node(additionalLabels: ["TechDB"]) { - name: String -} -# becomes -type Tech @node(labels: ["Tech", "TechDB"]) { - name: String -} ----- - -The equivalent of using both deprecated arguments is a list with all the values concatenated: - -[source, graphql, indent=0] ----- -type Tech @node(label: "TechDB", additionalLabels: ["AwesomeTech"]) { - name: String -} -# becomes -type Tech @node(labels: ["TechDB", "AwesomeTech"]) { - name: String -} ----- - -As before, providing none of these arguments results in the node label being the same as the GraphQL type name. - -Please note the implications on constraints. -In the following example, a unique constraint will be asserted for the label `Tech` and the property `name`: - -[source, graphql, indent=0] ----- -type Tech @node(labels: ["Tech", "TechDB"]) { - name: String @unique -} ----- - -=== `@queryOptions` removed and `limit` argument moved to `@limit` - -If you were using the `@queryOptions` directive to configure the default and max values for limiting the data returned by queries, for instance: - -[source, graphql, indent=0] ----- -type Record @queryOptions(limit: { default: 10, max: 100 }) { - id: ID! -} ----- - -This is now achieved by using the `@limit` directive: - -[source, graphql, indent=0] ----- -type Record @limit(default: 10, max: 100) { - id: ID! -} ----- - -=== `@readonly` and `@writeonly` removed - -The `@readonly` and `@writeonly` directives have been removed in favor of more granular configuration directives. -The new `@selectable` and `@settable` directives can be used to configure not only if fields are readable or writable, but also when they should be readable or writable. - -As a direct migration, the following usages are equivalent: - -* `@readonly` and `@settable(onCreate: false, onUpdate: false)`. -* `@writeonly` and `@selectable(onRead: false, onAggregate: false)`. - -[relationship-aggregate] -=== `@relationship` changes - -==== Relationship types are now automatically escaped - -Relationship types are now automatically escaped. If you have previously escaped your relationship types using backticks, you must now remove these as this is covered by the library. - -==== `aggregate` argument - -In version 4.0.0, the default value of the aggregate argument will be false. -This means that aggregation operation fields will no longer be generated by default when a relationship is defined using the `@relationship` directive. - -For instance, given the following type definitions: - -[source, graphql, indent=0] ----- -type Movie { - title: String! -} - -type Actor { - name: String! - actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT) -} ----- - -These will no longer generate `actedInAggregate` for the type `Actor`. - -To enable it, explicitly set the aggregate argument as `true`: - -[source, graphql, indent=0] ----- -type Movie { - title: String! -} - -type Actor { - name: String! - actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, aggregate: true) -} ----- - -=== `@relationshipProperties` now mandatory - -Upcoming changes to interfaces require us to distinguish between interfaces that are used to specify relationship properties, and others. Therefore, the `@relationshipProperties` directive is now required on all relationship property interfaces. -If it is not included, an error will be thrown. - -As a result, in version 4.0.0, the following type definitions are invalid: - -[source, graphql, indent=0] ----- -type Person { - name: String! - movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedIn") -} - -type Movie { - title: String! - actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") -} - -interface ActedIn { - screenTime: Int! -} ----- - -`ActedIn` must be decorated with `@relationshipProperties`: - -[source, graphql, indent=0] ----- -interface ActedIn @relationshipProperties { - screenTime: Int! -} ----- - -== Miscellaneous changes - -=== Duplicate relationship fields are now checked for - -It was possible to define schemas with types that have multiple relationship fields connected by the same type of relationships. Instances of this scenario are now detected during schema generation and an error is thrown so developers are informed to remedy the type definitions. - -An example of what is now considered invalid with these checks: - -[source, graphql, indent=0] ----- -type Team { - player1: Person! @relationship(type: "PLAYS_IN", direction: IN) - player2: Person! @relationship(type: "PLAYS_IN", direction: IN) - backupPlayers: [Person!]! @relationship(type: "PLAYS_IN", direction: IN) -} - -type Person { - teams: [Team!]! @relationship(type: "PLAYS_IN", direction: OUT) -} ----- - -In this example, there are multiple fields in the `Team` type which have the same `Person` type, the same `@relationship` type and ("PLAYS_IN") direction (IN). This is an issue when returning data from the database, as there would be no difference between `player1`, `player2` and `backupPlayers`. Selecting these fields would then return the same data. - -These checks can be disabled by disabling all validation in the library, however, this is not recommended unless in production with 100% confidence of type definitions input. - -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - validate: false, -}); ----- - -[[opt-in-aggregation]] -=== Opt-in Aggregation - -Aggregation operations are no longer generated by default. -They can be enabled case by case using the directives xref::/schema-configuration/type-configuration.adoc#_query[`@query`] and xref::/schema-configuration/field-configuration.adoc#_relationship[`@relationship`]. - -You can enable the operation fields `actorsAggregate` and `actedInAggregate` like this: - -[source, graphql, indent=0] ----- -type Movie { - title: String! -} - -type Actor @query(aggregate: true) { - name: String! - actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, aggregate: true) -} ----- - -=== `cypherParams` - -`cypherParams` is available in the context to provide the ability to pass arbitrary parameters to a custom Cypher query. This functionality remains in 4.0.0, but you no longer have to use the `$cypherParams` prefix to reference these parameters. From d328bd2d08e80b48bdf86068c70a43408ab4330e Mon Sep 17 00:00:00 2001 From: lidiazuin Date: Thu, 5 Oct 2023 15:46:14 +0200 Subject: [PATCH 26/28] fixing conflicts; --- .../ROOT/pages/migration/authorization.adoc | 13 ++---------- modules/ROOT/pages/migration/index.adoc | 20 +------------------ .../field-configuration.adoc | 2 +- .../type-configuration.adoc | 2 +- 4 files changed, 5 insertions(+), 32 deletions(-) diff --git a/modules/ROOT/pages/migration/authorization.adoc b/modules/ROOT/pages/migration/authorization.adoc index 1d31119b..424f269f 100644 --- a/modules/ROOT/pages/migration/authorization.adoc +++ b/modules/ROOT/pages/migration/authorization.adoc @@ -1,15 +1,6 @@ = Authentication and Authorization -<<<<<<< HEAD -<<<<<<< HEAD:modules/ROOT/pages/migration/authorization.adoc :description: This page describes the changes in authentication and authorization features in version 4.0.0 of the Neo4j GraphQL Library. :page-aliases: auth/global-authentication.adoc, migration/v4-migration/authorization.adoc -======= -:page-aliases: auth/global-authentication.adoc ->>>>>>> e533fd8 (Add page-aliases for version 4 (#31) (#32)):modules/ROOT/pages/migration/v4-migration/authorization.adoc -======= -:description: This page describes the changes in authentication and authorization features in version 4.0.0 of the Neo4j GraphQL Library. -:page-aliases: auth/global-authentication.adoc, migration/v4-migration/authorization.adoc ->>>>>>> 166b72c (Update to the migrations guide (#43)) The largest breaking change in version 4.0.0 is the removal of the `@auth` directive, which requires a migration to the new `@authentication`, `@authorization`, and `@subscriptionsAuthorization` directives. @@ -226,10 +217,10 @@ Note that `bind` is no longer a discrete rule, but configured by a `when` argume === `isAuthenticated` [WARNING] -==== +=== There isn't a direct replacement for the `isAuthenticated` argument. https://github.com/neo4j/graphql/issues/new/choose[Raise a feature request] if this is blocking migration. -==== +=== Given a previous type definition, which required authentication for any operation on the type `User`: diff --git a/modules/ROOT/pages/migration/index.adoc b/modules/ROOT/pages/migration/index.adoc index 4063f4a6..7b125081 100644 --- a/modules/ROOT/pages/migration/index.adoc +++ b/modules/ROOT/pages/migration/index.adoc @@ -1,30 +1,12 @@ -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> 166b72c (Update to the migrations guide (#43)) -[[v4-migration]] +[[migration-guide]] :description: This page lists the breaking changes from version 3.0.0 to 4.0.0 and describes how to update. -:page-aliases: guides/v4-migration/index.adoc , migration/v4-migration/index.adoc = Migration to 4.0.0 -<<<<<<< HEAD -======= -[[migration-guide]] -= Migration from `neo4j-graphql-js` :page-aliases: guides/index.adoc, guides/migration-guide/index.adoc, guides/migration-guide/server.adoc, guides/migration-guide/type-definitions.adoc, guides/migration-guide/mutations.adoc ->>>>>>> e533fd8 (Add page-aliases for version 4 (#31) (#32)) - -This page lists all breaking changes from the Neo4j GraphQL Library version 3.x to 4.x and how to update it. - -== How to update - -======= - This page lists all breaking changes from the Neo4j GraphQL Library version 3.x to 4.x and how to update it. == How to update ->>>>>>> 166b72c (Update to the migrations guide (#43)) To update your Neo4j GraphQL Library, use npm or the package manager of choice: [source, bash, indent=0] diff --git a/modules/ROOT/pages/schema-configuration/field-configuration.adoc b/modules/ROOT/pages/schema-configuration/field-configuration.adoc index 6683ac79..fe7e84aa 100644 --- a/modules/ROOT/pages/schema-configuration/field-configuration.adoc +++ b/modules/ROOT/pages/schema-configuration/field-configuration.adoc @@ -98,7 +98,7 @@ directive @relationship( [NOTE] ==== In version 4.0.0, `aggregate` is `false` as default. -See xref:migration/v4-migration/index.adoc#_relationship_changes[`@relationship changes`] for more information. +See xref:migration/index.adoc[`Relationship updates`] for more information. ==== === Usage diff --git a/modules/ROOT/pages/schema-configuration/type-configuration.adoc b/modules/ROOT/pages/schema-configuration/type-configuration.adoc index f5b61ded..c303ef19 100644 --- a/modules/ROOT/pages/schema-configuration/type-configuration.adoc +++ b/modules/ROOT/pages/schema-configuration/type-configuration.adoc @@ -52,7 +52,7 @@ directive @query(read: Boolean! = true, aggregate: Boolean! = false) on OBJECT | [NOTE] ==== Aggregations are no longer generated by default in the 4.0.0 version of the library. -See xref:migration/v4-migration/index.adoc#opt-in-aggregation[Opt-in aggregation] for more information. +See xref:migration/index.adoc#_updated_directives[Updated directives] for more information. ==== === Usage From 51c4304f3f0c77a9fcef2ef596e2f26665fd0b23 Mon Sep 17 00:00:00 2001 From: lidiazuin Date: Thu, 5 Oct 2023 15:50:25 +0200 Subject: [PATCH 27/28] fixing conflicts --- .../impersonation-and-user-switching.adoc | 2 +- modules/ROOT/pages/migration/ogm.adoc | 13 ------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/modules/ROOT/pages/authentication-and-authorization/impersonation-and-user-switching.adoc b/modules/ROOT/pages/authentication-and-authorization/impersonation-and-user-switching.adoc index dd4c3cb6..17d22b9c 100644 --- a/modules/ROOT/pages/authentication-and-authorization/impersonation-and-user-switching.adoc +++ b/modules/ROOT/pages/authentication-and-authorization/impersonation-and-user-switching.adoc @@ -7,7 +7,7 @@ Impersonation and user switching are features of the Neo4j database and driver w == Impersonation Impersonation still authenticates with the database as the original configured user, but runs the query in the context of an impersonated user. -When impersonating a user, the query is run within the complete security context of the impersonated user and not the authenticated user (home database, permissions etc). +When impersonating a user, the query is run within the complete security context of the impersonated user and not the authenticated user (home database, permissions, etc.). Consider the following an example of how to impersonate a different user per request. Here the user to impersonate is taken from a HTTP header `User`: diff --git a/modules/ROOT/pages/migration/ogm.adoc b/modules/ROOT/pages/migration/ogm.adoc index 2f3852be..28e2c9aa 100644 --- a/modules/ROOT/pages/migration/ogm.adoc +++ b/modules/ROOT/pages/migration/ogm.adoc @@ -1,22 +1,9 @@ = OGM -<<<<<<< HEAD -<<<<<<< HEAD:modules/ROOT/pages/migration/ogm.adoc -:description: This page describes what updates were made to the OGM tool in version 4.0.0 of the Neo4j GraphQL Library. -:page-aliases: guides/v4-migration/ogm.adoc, migration/v4-migration/ogm.adoc -======= -:page-aliases: guides/v4-migration/ogm.adoc - ->>>>>>> e533fd8 (Add page-aliases for version 4 (#31) (#32)):modules/ROOT/pages/migration/v4-migration/ogm.adoc - -This page describes what updates were made to the OGM tool in version 4.0.0 of the Neo4j GraphQL Library. - -======= :description: This page describes what updates were made to the OGM tool in version 4.0.0 of the Neo4j GraphQL Library. :page-aliases: guides/v4-migration/ogm.adoc, migration/v4-migration/ogm.adoc This page describes what updates were made to the OGM tool in version 4.0.0 of the Neo4j GraphQL Library. ->>>>>>> 166b72c (Update to the migrations guide (#43)) == Database specification The method to specify the database that the OGM should use has been changed. From c3215618423968fa660409d7e051b1f15464fc80 Mon Sep 17 00:00:00 2001 From: lidiazuin Date: Thu, 5 Oct 2023 15:55:09 +0200 Subject: [PATCH 28/28] fixing conflicts --- modules/ROOT/pages/subscriptions/events.adoc | 4 ++-- modules/ROOT/partials/reusing-content.adoc | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/ROOT/pages/subscriptions/events.adoc b/modules/ROOT/pages/subscriptions/events.adoc index 644da9fb..fffe3756 100644 --- a/modules/ROOT/pages/subscriptions/events.adoc +++ b/modules/ROOT/pages/subscriptions/events.adoc @@ -8,10 +8,10 @@ subscriptions/events/update.adoc This page covers a variety of subscription options offered by the Neo4j GraphQL Library. [NOTE] -=== +==== Only changes made through `@neo4j/graphql` should trigger the events here described. Changes made directly to the database or using the xref::type-definitions/directives/cypher.adoc[`@cypher` directive] will **not** trigger any event. -=== +==== == `CREATE` diff --git a/modules/ROOT/partials/reusing-content.adoc b/modules/ROOT/partials/reusing-content.adoc index 52df337f..464c8ddc 100644 --- a/modules/ROOT/partials/reusing-content.adoc +++ b/modules/ROOT/partials/reusing-content.adoc @@ -1,2 +1 @@ -[[include-typescript]] This paragraph can be used anywhere with the syntax shown in xref:content-types.adoc#_partials[]. \ No newline at end of file