4.0.0 Migration
Version 4.0.0 of the library has not yet been released. However, we recommend making these changes early in order to avoid issues in the future. |
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:
npm update @neo4j/graphql
Updated Directives
We have renamed a number of directives and their arguments, in order to make using @neo4j/graphql
more intuitive.
@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:
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:
type User {
id: ID! @populatedBy(callback: "nanoid", operations: [CREATE])
firstName: String!
surname: String!
}
Note that before and after these changes, a callback named nanoid
would need to be defined as below:
new Neo4jGraphQL({
typeDefs,
config: {
callbacks: {
nanoid: () => { return nanoid(); }
}
}
});
@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:
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:
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:
new Neo4jGraphQL({
typeDefs,
resolvers: {
User: {
fullName: ({ firstName, lastName }, args, context, info) => (`${firstName} ${lastName}`),
}
}
});
Checks for custom resolvers
Previously, if no custom resolver was specified for a @computed
field when creating an instance of Neo4jGraphQL, no errors would be thrown when generating the schema.
However, it is likely that the lack of a custom resolver would lead to errors at runtime. It is preferable to fail fast in this case as it is easier to debug and makes it less likely that bugs will make it into production.
As a result, checks are now performed to ensure that every @customResolver
field has a custom resolver provided. If not the library will throw an error during schema generation.
These checks may not always be required or desirable. If this is the case, they can be disabled using the new startupValidation
config option:
const neoSchema = new Neo4jGraphQL({
typeDefs,
config: {
startupValidation: {
resolvers: false
},
},
})
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:
type Tech @node(label: "TechDB", plural: "Techs") {
name: String
}
It would need to be updated to use the new directive as below:
type Tech @node(label: "TechDB") @plural(value: "Techs") {
name: String
}
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:
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:
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:
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
:
type Tech @node(labels: ["Tech", "TechDB"]) {
name: String @unique
}
@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:
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.
-
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:
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:
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:
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:
{
"data": {
"movieFulltextMovieTitle": [
{
"score": 0.44524085521698,
"movie": {
"title": "Full Moon High"
}
},
{
"score": 1.411118507385254,
"movie": {
"title": "Full Metal Jacket"
}
}
]
}
}
Argument changes
@fulltext
arguments:-
queryName
has been added to specify a custom name for the top-level query that is generated. -
name
has been renamed toindexName
to avoid ambiguity with the newqueryName
argument.
These changes means that the following type definition is now invalid:
type Movie @fulltext(indexes: [{ name: "MovieTitle", fields: ["title"] }]) {
title: String!
}
The name
argument would need to be replaced with indexName
as below:
type Movie @fulltext(indexes: [{ indexName: "MovieTitle", fields: ["title"] }]) {
title: String!
}
The queryName
argument can be used as below:
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
:
type Query {
moviesByTitle(phrase: String!, where: MovieFulltextWhere, sort: [MovieFulltextSort!], limit: Int, offset: Int): [MovieFulltextResult!]!
}
@cypher
changes
The default behaviour of the @cypher
directive regarding the translation will change: Instead of using 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:
type query {
test: String! @cypher(statement: "RETURN 'hello'")
}
Would get translated to:
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:
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:
type query {
test: String! @cypher(statement: "RETURN 'hello' as result", columnName: "result")
}
Additionally, escaping strings is no longer needed.
Miscellaneous changes
Startup validation
In version 4.0.0, startup checks for custom resolvers have been added. As a result, a new configuration option has been added that can disable these checks.
This new option has been combined with the option to skipValidateTypeDefs
. As a result, skipValidateTypeDefs
will be removed and replaced by startupValidation
.
To only disable strict type definition validation, the following config option should be used:
const neoSchema = new Neo4jGraphQL({
typeDefs,
config: {
startupValidation: {
typeDefs: false
},
},
})
To only disable checks for custom resolvers, the following config option should be used:
const neoSchema = new Neo4jGraphQL({
typeDefs,
config: {
startupValidation: {
resolvers: false
},
},
})
To disable all startup checks, the following config option should be used:
const neoSchema = new Neo4jGraphQL({
typeDefs,
config: {
startupValidation: false,
},
})
Was this page helpful?