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 79% rename from modules/ROOT/pages/migration/v4-migration/authorization.adoc rename to modules/ROOT/pages/migration/authorization.adoc index 415218db..424f269f 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,15 +214,13 @@ 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..7b125081 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` +:description: This page lists the breaking changes from version 3.0.0 to 4.0.0 and describes how to update. += Migration to 4.0.0 :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 +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/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/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/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 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/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