diff --git a/docs/antora/content-nav.adoc b/docs/antora/content-nav.adoc index 2bcd1eabc7..d6881f8f3f 100644 --- a/docs/antora/content-nav.adoc +++ b/docs/antora/content-nav.adoc @@ -1,6 +1,8 @@ * xref:index.adoc[] +** xref:introduction/index.adoc[] ** xref:getting-started/index.adoc[] ** xref:type-definitions/index.adoc[] +*** xref:type-definitions/basics/index.adoc[] *** xref:type-definitions/types/index.adoc[] *** xref:type-definitions/unions-and-interfaces/index.adoc[] *** xref:type-definitions/relationships/index.adoc[] @@ -8,43 +10,55 @@ *** xref:type-definitions/autogeneration/index.adoc[] *** xref:type-definitions/cypher/index.adoc[] *** xref:type-definitions/default-values/index.adoc[] -** xref:custom-resolvers/index.adoc[] -** xref:schema/index.adoc[] -*** xref:schema/queries/index.adoc[] -*** xref:schema/mutations/index.adoc[] -*** xref:schema/filtering/index.adoc[] -*** xref:schema/sorting/index.adoc[] -*** xref:schema/pagination/index.adoc[] ** xref:queries/index.adoc[] ** xref:mutations/index.adoc[] +*** xref:mutations/create/index.adoc[] +*** xref:mutations/update/index.adoc[] +*** xref:mutations/delete/index.adoc[] +** xref:filtering/index.adoc[] +** xref:sorting/index.adoc[] +** xref:pagination/index.adoc[] +*** xref:pagination/offset-based/index.adoc[] +*** xref:pagination/cursor-based/index.adoc[] +** xref:custom-resolvers/index.adoc[] ** xref:auth/index.adoc[] *** xref:auth/setup/index.adoc[] +*** xref:auth/auth-directive/index.adoc[] *** xref:auth/authentication/index.adoc[] *** xref:auth/authorization/index.adoc[] -**** xref:auth/authorization/roles/index.adoc[] **** xref:auth/authorization/allow/index.adoc[] -**** xref:auth/authorization/where/index.adoc[] **** xref:auth/authorization/bind/index.adoc[] +**** xref:auth/authorization/roles/index.adoc[] +**** xref:auth/authorization/where/index.adoc[] ** xref:directives/index.adoc[] ** xref:api-reference/index.adoc[] +*** xref:api-reference/neo4jgraphql/index.adoc[] +*** xref:api-reference/ogm/index.adoc[] ** xref:ogm/index.adoc[] -*** xref:ogm/getting-started/index.adoc[] -*** xref:ogm/methods/index.adoc[] -**** xref:ogm/methods/create/index.adoc[] -**** xref:ogm/methods/find/index.adoc[] -**** xref:ogm/methods/count/index.adoc[] -**** xref:ogm/methods/update/index.adoc[] -**** xref:ogm/methods/delete/index.adoc[] +*** xref:ogm/installation/index.adoc[] +*** xref:ogm/examples/index.adoc[] +**** xref:ogm/examples/custom-resolvers/index.adoc[] +**** xref:ogm/examples/rest-api/index.adoc[] *** xref:ogm/private/index.adoc[] *** xref:ogm/selection-set/index.adoc[] *** xref:ogm/api-reference/index.adoc[] -** xref:drivers-and-config/index.adoc[] +**** xref:ogm/api-reference/ogm/index.adoc[] +**** xref:ogm/api-reference/model/index.adoc[] +***** xref:ogm/api-reference/model/create/index.adoc[] +***** xref:ogm/api-reference/model/find/index.adoc[] +***** xref:ogm/api-reference/model/update/index.adoc[] +***** xref:ogm/api-reference/model/delete/index.adoc[] +***** xref:ogm/api-reference/model/count/index.adoc[] +** xref:driver-configuration/index.adoc[] ** xref:guides/index.adoc[] *** xref:guides/migration-guide/index.adoc[] **** xref:guides/migration-guide/server/index.adoc[] **** xref:guides/migration-guide/type-definitions/index.adoc[] **** xref:guides/migration-guide/queries/index.adoc[] **** xref:guides/migration-guide/mutations/index.adoc[] -*** xref:guides/rel-migration/index.adoc[] -**** xref:guides/rel-migration/mutations/index.adoc[] +*** xref:guides/v2-migration/index.adoc[] +**** xref:guides/v2-migration/mutations/index.adoc[] +**** xref:guides/v2-migration/unions/index.adoc[] +**** xref:guides/v2-migration/miscellaneous/index.adoc[] ** xref:troubleshooting/index.adoc[] +*** xref:troubleshooting/faqs/index.adoc[] diff --git a/docs/asciidoc/api-reference.adoc b/docs/asciidoc/api-reference.adoc deleted file mode 100644 index 6205816e1e..0000000000 --- a/docs/asciidoc/api-reference.adoc +++ /dev/null @@ -1,41 +0,0 @@ -[[api-reference]] -= API Reference - - -== `Neo4jGraphQL` -Main Entry to the library. Holds metadata about the GraphQL schema. - -=== Requiring -[source, javascript] ----- -const { Neo4jGraphQL } = require("@neo4j/graphql"); ----- - -=== Constructing - -[source, javascript] ----- -const neo4jGraphQL = new Neo4jGraphQL({ - typeDefs, - resolvers?, - schemaDirectives?, - driver?, - config?: { - driverConfig?, - enableRegex?, - jwt?: { - secret?, - noVerify?, - rolesPath?, - }, - }, -}); ----- - -=== Methods - -==== `checkNeo4jCompat` -Reference: <> - -== `OGM` -Reference: <> diff --git a/docs/asciidoc/api-reference/index.adoc b/docs/asciidoc/api-reference/index.adoc new file mode 100644 index 0000000000..b8b6f14d12 --- /dev/null +++ b/docs/asciidoc/api-reference/index.adoc @@ -0,0 +1,5 @@ +[[api-reference]] += API Reference + +- <> +- <> diff --git a/docs/asciidoc/api-reference/neo4jgraphql.adoc b/docs/asciidoc/api-reference/neo4jgraphql.adoc new file mode 100644 index 0000000000..77e775edb4 --- /dev/null +++ b/docs/asciidoc/api-reference/neo4jgraphql.adoc @@ -0,0 +1,194 @@ +[[api-reference-neo4jgraphql]] += `Neo4jGraphQL` + +== `constructor` + +Returns a `Neo4jGraphQL` instance. + +Takes an `input` object as a parameter, the supported fields of which are described below. + +=== Example + +[source, javascript] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, +}); +---- + +[[api-reference-neo4jgraphql-input]] +=== Input + +Accepts all of the options from https://github.com/ardatan/graphql-tools/blob/%40graphql-tools/schema%407.1.5/website/docs/generate-schema.md#makeexecutableschemaoptions[`makeExecutableSchema`], plus the additional arguments below: + +|=== +|Name and Type |Description + +|`driver` + + + + Type: https://neo4j.com/docs/javascript-manual/current/[`Driver`] +|An instance of a Neo4j driver. + +|`config` + + + + Type: `Neo4jGraphQLConfig` +|Additional Neo4j GraphQL configuration options. +|=== + +[[api-reference-neo4jgraphql-input-neo4jgraphqlconfig]] +==== `Neo4jGraphQLConfig` + +|=== +|Name and Type |Description + +|`driverConfig` + + + + Type: <> +|Additional driver configuration options. + +|`enableRegex` + + + + Type: `boolean` +|Whether to enable RegEx filters, see <> for more information. + +|`jwt` + + + + Type: <> +|JWT options. + +|`queryOptions` + + + + Type: <> +|Cypher query options, see <> for more information. + +|`skipValidateTypeDefs` + + + + Type: `boolean` +|Can be used to disable strict type definition validation if you are encountering unexpected errors. +|=== + +[[api-reference-neo4jgraphql-input-neo4jgraphqlconfig-driverconfig]] +===== `DriverConfig` + +|=== +|Name and Type |Description + +|`database` + + + + Type: `string` +|The name of the database within the DBMS to connect to. + +|`bookmarks` + + + + Type: `string` or `Array` +|One or more bookmarks to use for the connection. +|=== + +[[api-reference-neo4jgraphql-input-neo4jgraphqlconfig-neo4jgraphqljwt]] +===== `Neo4jGraphQLJWT` + +|=== +|Name and Type |Description + +|`secret` + + + + Type: `string` +|The secret used to encode JWT tokens. + + + + *Required* unless passing in decoded tokens. + +|`noVerify` + + + + Type: `boolean` +|Disable verification of JWT signatures, only decode. + +|`rolesPath` + + + + Type: `string` +|Dot path of location of roles within JWT token. +|=== + +[[api-reference-neo4jgraphql-input-neo4jgraphqlconfig-cypherqueryoptions]] +===== `CypherQueryOptions` + +All options are enum types imported from `@neo4j/graphql`, for example: + +[source, javascript] +---- +const { CypherRuntime } = require("@neo4j/graphql"); +---- + +|=== +|Name and Type |Description + +|`runtime` + + + + Type: `CypherRuntime` +|Possible options: + + + + - `CypherRuntime.INTERPRETED` + + - `CypherRuntime.SLOTTED` + + - `CypherRuntime.PIPELINED` + +|`planner` + + + + Type: `CypherPlanner` +|Possible options: + + + + - `CypherPlanner.COST` + + - `CypherPlanner.IDP` + + - `CypherPlanner.DP` + +|`connectComponentsPlanner` + + + + Type: `CypherConnectComponentsPlanner` +|Possible options: + + + + - `CypherConnectComponentsPlanner.GREEDY` + + - `CypherConnectComponentsPlanner.IDP` + +|`updateStrategy` + + + + Type: `CypherUpdateStrategy` +|Possible options: + + + + - `CypherUpdateStrategy.DEFAULT` + + - `CypherUpdateStrategy.EAGER` + +|`expressionEngine` + + + + Type: `CypherExpressionEngine` +|Possible options: + + + + - `CypherExpressionEngine.DEFAULT` + + - `CypherExpressionEngine.INTERPRETED` + + - `CypherExpressionEngine.COMPILED` + +|`operatorEngine` + + + + Type: `CypherOperatorEngine` +|Possible options: + + + + - `CypherOperatorEngine.DEFAULT` + + - `CypherOperatorEngine.INTERPRETED` + + - `CypherOperatorEngine.COMPILED` + +|`interpretedPipesFallback` + + + + Type: `CypherInterpretedPipesFallback` +|Possible options: + + + + - `CypherInterpretedPipesFallback.DEFAULT` + + - `CypherInterpretedPipesFallback.DISABLED` + + - `CypherInterpretedPipesFallback.WHITELISTED_PLANS_ONLY` + + - `CypherInterpretedPipesFallback.ALL` + +|`replan` + + + + Type: `CypherReplanning` +|Possible options: + + + + - `CypherReplanning.DEFAULT` + + - `CypherReplanning.FORCE` + + - `CypherReplanning.SKIP` +|=== diff --git a/docs/asciidoc/api-reference/ogm.adoc b/docs/asciidoc/api-reference/ogm.adoc new file mode 100644 index 0000000000..de1f6345a6 --- /dev/null +++ b/docs/asciidoc/api-reference/ogm.adoc @@ -0,0 +1,4 @@ +[[api-reference-ogm]] += `@neo4j/graphql-ogm` + +See <>. diff --git a/docs/asciidoc/auth/auth-directive.adoc b/docs/asciidoc/auth/auth-directive.adoc new file mode 100644 index 0000000000..417c4ba773 --- /dev/null +++ b/docs/asciidoc/auth/auth-directive.adoc @@ -0,0 +1,84 @@ +[[auth-directive]] += `@auth` directive + +The `@auth` directive definition is dynamically generated on runtime based on user type definitions. + +== `rules` + +You can have many rules for many operations. We fall through each rule, until we find a match to the corresponding operation. If no match is found, an error is thrown. You can think of rules as a big `OR`. + +[source, graphql] +---- +@auth(rules: [ + { operations: [CREATE, UPDATE], ... }, ## or + { operations: [READ, UPDATE], ...}, ## or + { operations: [DELETE, UPDATE], ... } ## or +]) +---- + +== `operations` + +`operations` is an array which allows you to re-use the same rule for many operations. + +[source, graphql] +---- +@auth(rules: [ + { operations: [CREATE, UPDATE, DELETE, CONNECT, DISCONNECT] }, + { operations: [READ] } +]) +---- + +NOTE: Note that the absence of an `operations` argument will imply _all_ operations. + +Many different operations can be called at once, for example in the following Mutation: + +[source, graphql] +---- +mutation { + createPosts( + input: [ + { + content: "I like GraphQL", + creator: { connect: { where: { id: "user-01" } } } + } + ] + ) { + posts { + content + } + } +} +---- + +In the above example, we perform a `CREATE` followed by a `CONNECT`, so our auth rule must allow our user to perform both of these operations. + +The full list of operations and how they related to Cypher clauses are: + +|=== +|Operation |Cypher clause(s) + +|`READ` +|`MATCH` + +|`CREATE` +|`CREATE` + +|`UPDATE` +|`SET` + +|`DELETE` +|`DELETE` + +|`CONNECT` +|`MATCH` and `MERGE` + +|`DISCONNECT` +|`MATCH` and `DELETE` +|=== + +== Auth Value Plucking + +When using the `@auth` directive, you use the following prefixes to substitute in their relevant values: + +- `$jwt.` - pulls value from JWT +- `$context.` - pulls value from context diff --git a/docs/asciidoc/auth/authentication.adoc b/docs/asciidoc/auth/authentication.adoc index 9bc32dadf6..84a71aaf06 100644 --- a/docs/asciidoc/auth/authentication.adoc +++ b/docs/asciidoc/auth/authentication.adoc @@ -1,87 +1,13 @@ [[auth-authentication]] = Authentication -Neo4j GraphQL will expect there to be an `authorization` header in the request object, which means you can authenticate users however you like. You could; Have a custom sign-in mutation, integrate with Auth0, or roll your own SSO server. The point here is that it’s just a JWT, in the library, we will decode it to make sure it’s valid - but it’s down to you to issue tokens. +The Neo4j GraphQL Library expects an `authorization` header in the request object, which means you can authenticate users however you like. You could have a custom sign-in mutation, integrate with Auth0, or roll your own SSO server. The point here is that it’s just a JWT which the library decodes to make sure it’s valid - but it’s down to the user to issue tokens. -== OGM - -Here we will use the <> to set up a hypothetical sign-in flow; - - -[source, javascript] ----- -const { Neo4jGraphQL } = require("@neo4j-graphql"); -const { createJWT, comparePassword } = require("./utils"); // example -const { ApolloServer } = require("apollo-server"); -const { OGM } = require("@neo4j-graphql-ogm"); - -const typeDefs = ` - type User { - id: ID @id - email: String! - password: String! - } - - type Mutation { - signIn(email: String!, password: String!): String ## token - } -`; - -const driver = neo4j.driver("bolt://localhost:7687", neo4j.auth.basic("admin", "password")); - -const ogm = new OGM({ - typeDefs, - driver, -}); - -const User = ogm.model("User"); - -const resolvers = { - Mutation: { - async signIn(root, { email, password }) { - const [existing] = await User.find({ - where: { - email, - }, - }); - - if (!existing) { - throw new Error("not found"); - } - - const equal = await comparePassword(password, existing.password); - if (!equal) { - throw new Error("bad password"); - } - - return createJWT({ - sub: user.id, - }); - }, - }, -}; - -const neoSchema = new Neo4jGraphQL({ - typeDefs, - resolvers, - driver, - config: { - jwt: { - secret - } - } -}); - -const server = new ApolloServer({ - schema: neoSchema.schema, - context: ({ req }) => ({ req }), -}); - -server.listen(4000).then(() => console.log("online")); ----- +> The example at <> demonstrates a hypothetical sign-up/sign-in flow using the <>, which will be a good starting point for inspiration. == `isAuthenticated` -This is the most basic of auth. Used to ensure that there is a valid decoded JWT in the request. The most basic of applications could look something like this; + +This is the most basic of authentication, used to ensure that there is a valid decoded JWT in the request. The most basic of type definitions could look something like the following, which states you must be authenticated to access `Todo` objects: [source, graphql] ---- @@ -94,9 +20,10 @@ extend type Todo @auth(rules: [{ isAuthenticated: true }]) ---- == `allowUnauthenticated` + In some cases, you may want to allow unauthenticated requests while also having auth-based rules. You can use the `allowUnauthenticated` parameter to avoid throwing an exception if no auth is present in the context. -In the example below, only the publisher can see his blog posts if it is not published yet. Once the blog post is published, anyone can see it. +In the example below, only the publisher can see his blog posts if it is not published yet. Once the blog post is published, anyone can see it: [source, graphql] ---- diff --git a/docs/asciidoc/auth/authorization/allow.adoc b/docs/asciidoc/auth/authorization/allow.adoc index 58ec5f113b..9fe3be3180 100644 --- a/docs/asciidoc/auth/authorization/allow.adoc +++ b/docs/asciidoc/auth/authorization/allow.adoc @@ -1,14 +1,16 @@ [[auth-authorization-allow]] = Allow -Use `allow` to ensure that on matched nodes, a connection exists between a value on the JWT and a property on each matched node. Taking a closer look, let's create two users in a hypothetical empty database; +Use `allow` to ensure that on matched nodes, there is equality between a value on the JWT and a property on each matched node. Taking a closer look, let's create two users in a hypothetical empty database: [source, cypher] ---- -CREATE (:User {id:"user1", name: "one"}) -CREATE (:User {id:"user2", name: "two"}) +CREATE (:User { id: "user1", name: "one" }) +CREATE (:User { id: "user2", name: "two" }) ---- +For the label and properties of the nodes created above, we would have the equivalent GraphQL type definition as follows: + [source, graphql] ---- type User { @@ -17,7 +19,7 @@ type User { } ---- -Now we have two users in our database, and given the above GraphQL type definitions - How can we restrict `user1` from accessing `user2`? This is where `allow` comes in; +Now that we have two users in our database, and a simple type definition - let's say we need to restrict `user1` from accessing `user2`. This is where `allow` comes in: [source, graphql] ---- @@ -36,18 +38,20 @@ extend type User @auth( ) ---- -After we match the node, we validate that the property on the node is equal to the `jwt.sub` property. This validation is done in Cypher with two functions; `validatePredicate` and `validate`. +After we match the node, we validate that the property `id` on the node is equal to the `jwt.sub` property. + +Given `user1` has the following decoded JWT: -Given `user1` has the decoded JWT; [source, json] ---- { - "sub": "user1", - "iat": 1516239022 + "sub": "user1", + "iat": 1516239022 } ---- -With this JWT makes a GraphQL query to get `user2`; +If "user1" used this JWT in a request for "user2": + [source, graphql] ---- query { @@ -57,26 +61,26 @@ query { } ---- -The generated cypher for this query would look like the below and throw you out the operation. +The generated cypher for this query would look like the following and throw you out the operation: [source, cypher] ---- -MATCH (u:User {id: "user2"}) +MATCH (u:User { id: "user2" }) CALL apoc.util.validate(NOT(u.id = "user1"), "Forbidden") RETURN u ---- -Allow is used on the following operations; +Allow is available on the following operations: -1. read -2. update -3. connect -4. disconnect -5. delete +- `READ` +- `UPDATE` +- `CONNECT` +- `DISCONNECT` +- `DELETE` -== `allow` Across Relationships +== `allow` across relationships -There may be a reason where you need to traverse across relationships to satisfy your auth implementation. One example of this could be "Grant update access to all Moderators of a Post"; +There may be a reason where you need to traverse across relationships to satisfy your authorization implementation. One example use case could be "grant update access to all Moderators of a Post": [source, graphql] ---- @@ -95,9 +99,9 @@ extend type Post @auth(rules: [ ]) ---- -When you specify allow on a relationship you can select fields on the referenced node. It's worth pointing out that allow on a relationship will perform an `ANY` on the matched nodes; to see if there is a match. +When you specify allow on a relationship you can select fields on the referenced node. It's worth pointing out that allow on a relationship will perform an `ANY` on the matched nodes to see if there is a match. -Given the above example - There may be a time when you need to give update access to either the creator of a post or a moderator, you can use `OR` and `AND` inside allow; +Given the above example - There may be a time when you need to give update access to either the creator of a post or a moderator, you can use `OR` and `AND` inside `allow`: [source, graphql] ---- @@ -123,9 +127,9 @@ extend type Post ) ---- -== Field Level `allow` +== Field-level `allow` -Allow works the same as it does on Type Definitions although its context is the Field. So instead of enforcing auth rules when the node is matched and/or upserted, it would instead be called when the Field is selected or upserted. Given the following, it is hiding the password to only the user themselves; +`allow` works the same as it does on Types although its context is the Field. So instead of enforcing auth rules when the node is matched and/or modified, it would instead be called when the Field is match and/or modified. Given the following, it is hiding the password to all users but the user themselves: [source, graphql] ---- diff --git a/docs/asciidoc/auth/authorization/bind.adoc b/docs/asciidoc/auth/authorization/bind.adoc index c294d58f59..8590372660 100644 --- a/docs/asciidoc/auth/authorization/bind.adoc +++ b/docs/asciidoc/auth/authorization/bind.adoc @@ -1,13 +1,15 @@ [[auth-authorization-bind]] = Bind -Use bind to ensure that on creating or updating nodes, a connection exists between a value on the JWT vs a property on a matched node. This validation is done after the operation but inside a transaction. Taking a closer look, let's put a user in our database; +Use bind to ensure that on creating or updating nodes, there is equality between a value on the JWT and a property on a matched node. This validation is done after the operation but inside a transaction. Taking a closer look, let's create a user in our database: [source, cypher] ---- -CREATE (:User {id:"user1", name: "one"}) +CREATE (:User { id:"user1", name: "one" }) ---- +For the label and properties of the node created above, we would have the equivalent GraphQL type definition as follows: + [source, graphql] ---- type User { @@ -16,8 +18,7 @@ type User { } ---- - -Given the above GraphQL type definitions - How can we restrict `user1` from changing their ID? +Given the above GraphQL type definition - we can restrict `user1` from changing their own ID: [source, graphql] ---- @@ -36,20 +37,19 @@ extend type User @auth( ) ---- -After we update or create the node we validate that the property on the node is equal to the `jwt.sub` property. This validation is done in Cypher with function `apoc.util.validate` +After we update or create the node we validate that the property `id` on the node is equal to the `jwt.sub` property. -Given `user1` has the decoded JWT; +Given `user1` has the following decoded JWT: [source, json] ---- { - "sub": "user1", - "iat": 1516239022 + "sub": "user1", + "iat": 1516239022 } ---- -With this JWT makes a GraphQL mutation to update their ID to someone else; - +When the user makes a request using this JWT to change their ID: [source, graphql] ---- @@ -62,30 +62,27 @@ mutation { } ---- -The generated cypher for this query would look like the below, Throwing us out of the operation because the ids do not match. - +The generated cypher for this query would look like the below, throwing us out of the operation because the `id` property no longer matches. [source, cypher] ---- -MATCH (u:User {id: "user1"}) +MATCH (u:User { id: "user1" }) SET u.id = "user2" CALL apoc.util.validate(NOT(u.id = "user1"), "Forbidden") RETURN u ---- +Bind is available for the following operations; -Bind is used on the following operations; - -1. create -2. update -3. connect -4. disconnect -5. delete - +- `READ` +- `UPDATE` +- `CONNECT` +- `DISCONNECT` +- `DELETE` -== `bind` Across Relationships +== `bind` across relationships -There may be a reason where you need to traverse across relationships to satisfy your Auth implementation. One example of this could be "Ensure that users only create Posts related to themselves"; +There may be a reason where you need to traverse across relationships to satisfy your authorization implementation. One use case could be "ensure that users only create Posts related to themselves": [source, graphql] ---- @@ -104,11 +101,11 @@ extend type Post @auth(rules: [ ]) ---- -When you specify `bind` on a relationship you can select fields on the referenced node. It's worth pointing out that allow on a relationship will perform an `ALL` on the matched nodes; to see if there is a match. This means you can only use `bind` to enforce a single relationship to a single node. +When you specify `bind` on a relationship you can select fields on the related node. It's worth pointing out that `bind` on a relationship field will perform an `ALL` on the matched nodes to see if there is a match. This means you can only use `bind` to enforce a single relationship to a single node. -=== Field Level `bind` +=== Field-level `bind` -You can use bind on a field. The root is still considered the node. Taking the example at the start of this `bind` section; you could do the following; +You can use `bind` on a field, and the root is still considered the node itself. Taking the example at the start of this chapter, you could do the following to implement the same behaviour: [source, graphql] ---- diff --git a/docs/asciidoc/auth/authorization/index.adoc b/docs/asciidoc/auth/authorization/index.adoc index 4e1a01ff76..f33493f68c 100644 --- a/docs/asciidoc/auth/authorization/index.adoc +++ b/docs/asciidoc/auth/authorization/index.adoc @@ -2,3 +2,8 @@ = Authorization You specify authorization rules inside the `@auth` directive. This section looks at each option available and explains how to use it to implement authorization. + +- <> +- <> +- <> +- <> diff --git a/docs/asciidoc/auth/authorization/roles.adoc b/docs/asciidoc/auth/authorization/roles.adoc index 50f52d5cf7..2dd898ff3b 100644 --- a/docs/asciidoc/auth/authorization/roles.adoc +++ b/docs/asciidoc/auth/authorization/roles.adoc @@ -1,7 +1,9 @@ [[auth-authorization-roles]] = Roles -Use the roles property to specify the allowed roles for an operation. Use config `rolesPath` to specify a object path for JWT roles otherwise defaults to `jwt.roles` +Use the `roles` property to specify the allowed roles for an operation. Use the `Neo4jGraphQL` config option `rolesPath` to specify a object path for JWT roles otherwise defaults to `jwt.roles`. + +The following type definitions show that an admin role is required for all update operations against Users. [source, graphql] ---- @@ -13,19 +15,16 @@ type User { extend type User @auth(rules: [{ operations: [UPDATE], roles: ["admin"] }]) ---- -Above showing an admin role is required for all operations against Users. If you have multiple roles you can add more items to the array; +If there are multiple possible roles you can add more items to the array, of which users only need one to satisfy a rule: [source, graphql] ---- -extend type User @auth(rules: [{ roles: ["admin", "super-admin"] }]) +extend type User @auth(rules: [{ operations: [UPDATE], roles: ["admin", "super-admin"] }]) ---- - -> Users only need one of many roles to satisfy a rule. - == RBAC -Here is a RBAC example using `roles`; +Here is an example of RBAC (Role-Based Access Control) using `roles`: [source, graphql] ---- @@ -45,4 +44,4 @@ type Invoice @auth(rules: [{ operations: [READ], roles: ["read:invoice"] }]) { csv: String total: Int } ----- \ No newline at end of file +---- diff --git a/docs/asciidoc/auth/authorization/where.adoc b/docs/asciidoc/auth/authorization/where.adoc index db187bf685..8baf031d78 100644 --- a/docs/asciidoc/auth/authorization/where.adoc +++ b/docs/asciidoc/auth/authorization/where.adoc @@ -1,7 +1,7 @@ [[auth-authorization-where]] = Where -Use the `where` argument, on Node definitions, to conceptually append predicates to the Cypher `WHERE` clause. Given the current user ID is "123" and the following the schema; +Use the `where` argument on types to conceptually append predicates to the Cypher `WHERE` clause. Given the current user ID is "123" and the following schema: [source, graphql] ---- @@ -13,7 +13,7 @@ type User { extend type User @auth(rules: [{ where: { id: "$jwt.id" } }]) ---- -Then issues a GraphQL query for users; +Then the user executes a GraphQL query for all users: [source, graphql] ---- @@ -25,7 +25,7 @@ query { } ---- -Behind the scenes the user’s ID is **conceptually** prepended to the query; +Behind the scenes the user’s ID is conceptually added to the query: [source, graphql] ---- @@ -39,8 +39,8 @@ query { Where is used on the following operations; -1. read -2. update -3. connect -4. disconnect -5. delete +- `READ` +- `UPDATE` +- `CONNECT` +- `DISCONNECT` +- `DELETE` diff --git a/docs/asciidoc/auth/index.adoc b/docs/asciidoc/auth/index.adoc index 0578275d94..9b9f0feae2 100644 --- a/docs/asciidoc/auth/index.adoc +++ b/docs/asciidoc/auth/index.adoc @@ -1,10 +1,16 @@ [[auth]] = Auth -In this section you will learn more about how to secure your GraphQL API using Neo4j GraphQL's inbuilt auth mechanics. +In this chapter you will learn more about how to secure your GraphQL API using the Neo4j GraphQL Library's built-in auth mechanics. -== Preview +- <> +- <> +- <> +- <> +== Quickstart examples + +Only authenticated users can create Post nodes: [source, graphql] ---- @@ -15,7 +21,7 @@ type Post @auth(rules: [ } ---- -When you have production-style Auth the directive can get large and complicated. Use Extend to tackle this; +Use `extend` to avoid large and unwieldy type definitions: [source, graphql] ---- @@ -28,7 +34,7 @@ extend type Post @auth(rules: [ ]) ---- -You can use the directive on 'Type Definitions', as seen in the example above, you can also apply the directive on any field so as long as it's not a `@relationship`; +You can use the directive types as seen in the example above, but you can also apply the directive on any field so as long as it's not decorated with `@relationship`. In the following example, the password field is only accessible to users with role "admin", or the user themselves: [source, graphql] ---- diff --git a/docs/asciidoc/auth/setup.adoc b/docs/asciidoc/auth/setup.adoc index debfa0b109..1bd6f229a7 100644 --- a/docs/asciidoc/auth/setup.adoc +++ b/docs/asciidoc/auth/setup.adoc @@ -23,9 +23,10 @@ const neoSchema = new Neo4jGraphQL({ }); ---- -It is also possible to pass in JWTs which have already been decoded, in which case the `jwt` option is _not necessary_. This will be covered in the section <>. +It is also possible to pass in JWTs which have already been decoded, in which case the `jwt` option is _not necessary_. This is covered in the section <> below. === Auth Roles Object Paths + If you are using a 3rd party auth provider such as Auth0 you may find your roles property being nested inside an object: [source, json] @@ -55,7 +56,7 @@ const neoSchema = new Neo4jGraphQL({ [[auth-setup-passing-in]] == Passing in JWTs -If you wish to pass in an encoded JWT, this must be included in the `Authorization` header of your requests, in the format: +If you wish to pass in an encoded JWT, this must be included in the `authorization` header of your requests, in the format: [source] ---- @@ -111,72 +112,7 @@ const server = new ApolloServer({ }); ---- -== `@auth` directive - -=== `rules` - -You can have many rules for many operations. We fall through each rule, on the corresponding operation, until we find a match. On no match found, an error is thrown. You can think of rules as a big OR. - -[source, graphql] ----- -@auth(rules: [ - { operations: [CREATE, UPDATE], ... }, ## or - { operations: [READ, UPDATE], ...}, ## or - { operations: [DELETE, UPDATE], ... } ## or -]) ----- - -=== `operations` - -Operations is an array which allows you to re-use the same rule for many operations. - -[source, graphql] ----- -@auth(rules: [ - { operations: [CREATE, UPDATE, DELETE, CONNECT, DISCONNECT] }, - { operations: [READ] } -]) ----- - -NOTE: Note that the absence of an `operations` argument will imply _all_ operations. - -Many different operations can be called at once, for example in the following Mutation: - -[source, graphql] ----- -mutation { - createPosts( - input: [ - { - content: "I like GraphQL", - creator: { connect: { where: { id: "user-01" } } } - } - ] - ) { - posts { - content - } - } -} ----- - -In the above example, we perform a `CREATE` followed by a `CONNECT`, so our auth rule must allow our user to perform both of these operations. - -The full list of operations are: - -- read - `MATCH` -- create - `CREATE` -- update - `SET` -- delete - `DELETE` -- connect - `MATCH` & `MERGE` -- disconnect - `MATCH` & `DELETE` - -== Auth Value Plucking - -1. `$jwt.` - Pulls value from jsonwebtoken -2. `$context.` - Pulls value from context - -== Auth Custom Resolvers +== Auth and Custom Resolvers You can't use the `@auth` directive on a custom resolver, however, we do make life easier by injecting the auth parameter into it. It will be available under the `context.auth` property. For example, the following custom resolver returns the `sub` field from the JWT: @@ -190,16 +126,16 @@ const typeDefs = ` const resolvers = { Query: { - myId(root, args, context) { + myId(_source, _args, context) { return context.auth.jwt.sub } } }; ---- -== Auth on `@cypher` +== Auth and `@cypher` fields -You can put the `@auth` directive on a field with the `@cypher` directive. Functionality like `allow` and `bind` will not work but you can still utilize `isAuthenticated` and `roles`. Additionally, you don't need to specify operations for `@auth` directives on `@cypher` fields. +You can put the `@auth` directive on a field alongside the `@cypher` directive. Functionality like `allow` and `bind` will not work but you can still utilize `isAuthenticated` and `roles`. Additionally, you don't need to specify `operations` for `@auth` directives on `@cypher` fields. The following example uses the `isAuthenticated` rule to ensure a user is authenticated, before returning the `User` associated with the JWT: @@ -211,7 +147,9 @@ type User @exclude { } type Query { - me: User @cypher(statement: "MATCH (u:User { id: $auth.jwt.sub }) RETURN u") @auth(rules: [{ isAuthenticated: true }]) + me: User + @cypher(statement: "MATCH (u:User { id: $auth.jwt.sub }) RETURN u") + @auth(rules: [{ isAuthenticated: true }]) } ---- diff --git a/docs/asciidoc/custom-resolvers.adoc b/docs/asciidoc/custom-resolvers.adoc index 6c48357a1e..5e22618747 100644 --- a/docs/asciidoc/custom-resolvers.adoc +++ b/docs/asciidoc/custom-resolvers.adoc @@ -23,8 +23,8 @@ const typeDefs = ` const resolvers = { User: { - fullName(obj) { - return `${obj.firstName} ${obj.lastName}`; + fullName(source) { + return `${source.firstName} ${source.lastName}`; }, }, }; @@ -37,28 +37,4 @@ const neoSchema = new Neo4jGraphQL({ == Custom Query/Mutation type field resolver -You can define additional, custom Queries and Mutations in your type definitions and provide custom resolvers for them. A prime use case for this is using the <> to manipulate types and fields which are not available through the API. - -[source, javascript] ----- -const typeDefs = ` - type User { - userId: ID! - } - - type Query { - users: [User] - } -`; - -const resolvers = { - Query: { - users: () => // implement resolver here - } -}; - -const neoSchema = new Neo4jGraphQL({ - typeDefs, - resolvers, -}); ----- +You can define additional custom Query and Mutation fields in your type definitions and provide custom resolvers for them. A prime use case for this is using the <> to manipulate types and fields which are not available through the API. You can find an example of it being used in this capacity in the <> example. diff --git a/docs/asciidoc/directives.adoc b/docs/asciidoc/directives.adoc index 78cbb7de38..1a009d334e 100644 --- a/docs/asciidoc/directives.adoc +++ b/docs/asciidoc/directives.adoc @@ -5,7 +5,7 @@ The `@auth` directive is used to define complex fine-grained and role-based access control for object types and fields. -Reference: <> +Reference: <> == `@coalesce` diff --git a/docs/asciidoc/driver-and-config.adoc b/docs/asciidoc/driver-and-config.adoc deleted file mode 100644 index 593782b3ab..0000000000 --- a/docs/asciidoc/driver-and-config.adoc +++ /dev/null @@ -1,93 +0,0 @@ -[[drivers-and-config]] -= Driver Configuration - - -== Neo4j Driver -The https://github.com/neo4j/neo4j-javascript-driver[Neo4j javascript driver] must be present in either the context or construction of your `Neo4jGraphQL` API or at the construction of your `OGM`. - -=== `Neo4jGraphQL` -[source, javascript] ----- -const { Neo4jGraphQL } = require("@neo4j/graphql"); -const neo4j = require("neo4j-driver"); - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("neo4j", "letmein") -); - -const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); - -const server = new ApolloServer({ - schema: neoSchema.schema, - context: ({ req }) => ({ req }), -}); ----- - -Or you can specify the driver at runtime using the context; - -[source, javascript] ----- -const server = new ApolloServer({ - schema: neoSchema.schema, - context: ({ req }) => ({ req, context }), -}); ----- - -=== `OGM` - -[source, javascript] ----- -const express = require("express"); -const { OGM } = require("@neo4j/graphql"); -const neo4j = require("neo4j-driver"); - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("neo4j", "letmein") -); - -const ogm = new OGM({ typeDefs, driver }); ----- - -[[drivers-and-config-checkNeo4jCompat]] -== `checkNeo4jCompat` -Use the `checkNeo4jCompat` method available on either `Neo4jGraphQL` or the `OGM` to ensure the specified DBMS has the required; versions, functions and procedures. - -==== `Neo4jGraphQL` - -[source, javascript] ----- -const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); -await neoSchema.checkNeo4jCompat(); ----- - -==== `OGM` - -[source, javascript] ----- -const ogm = new OGM({ typeDefs, driver }); -await ogm.checkNeo4jCompat(); ----- - -== Specifying Neo4j Database -The Neo4j database may be added to the GraphQL context object; - -[source, javascript] ----- -const server = new ApolloServer({ - schema, - context: { driver, driverConfig: { database: "sanmateo" } } -}); ----- - -== Specifying Neo4j Bookmarks -A Neo4j driver bookmark may be added to the GraphQL context object; - -[source, javascript] ----- -const server = new ApolloServer({ - schema, - context: { driver, driverConfig: { bookmarks: ["my-bookmark"] } } -}); ----- diff --git a/docs/asciidoc/driver-configuration.adoc b/docs/asciidoc/driver-configuration.adoc new file mode 100644 index 0000000000..c1ad523374 --- /dev/null +++ b/docs/asciidoc/driver-configuration.adoc @@ -0,0 +1,257 @@ +[[driver-configuration]] += Driver Configuration + +== Neo4j Driver +An instance of the https://github.com/neo4j/neo4j-javascript-driver[Neo4j JavaScript driver] must be present in either the GraphQL request context, or construction of your `Neo4jGraphQL` instance (or alternatively, `OGM`). + +The examples in this chapter assume a Neo4j database running at "bolt://localhost:7687" with a username of "neo4j" and a password of "password". + +=== Neo4j GraphQL Library + +==== Driver in `Neo4jGraphQL` constructor + +[source, javascript] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const neo4j = require("neo4j-driver"); + +const typeDefs = ` + 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: neoSchema.schema, + context: ({ req }) => ({ req }), +}); +---- + +==== Driver in context + +[source, javascript] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const neo4j = require("neo4j-driver"); + +const typeDefs = ` + 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: neoSchema.schema, + context: ({ req }) => ({ req, driver }), +}); +---- + +=== OGM + +[source, javascript] +---- +const { OGM } = require("@neo4j/graphql-ogm"); +const neo4j = require("neo4j-driver"); + +const typeDefs = ` + type User { + name: String + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const ogm = new OGM({ typeDefs, driver }); +---- + +[[driver-configuration-database-compatibility]] +== Database Compatibility + +Use the `checkNeo4jCompat` method available on either a `Neo4jGraphQL` or `OGM` instance to ensure the specified DBMS is of the required version, and has the necessary functions and procedures available. The `checkNeo4jCompat` will throw an `Error` if the DBMS is incompatible, with details of the incompatibilities. + +=== `Neo4jGraphQL` + +[source, javascript] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const neo4j = require("neo4j-driver"); + +const typeDefs = ` + type User { + name: String + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); +await neoSchema.checkNeo4jCompat(); +---- + +=== `OGM` + +[source, javascript] +---- +const { OGM } = require("@neo4j/graphql-ogm"); +const neo4j = require("neo4j-driver"); + +const typeDefs = ` + type User { + name: String + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const ogm = new OGM({ typeDefs, driver }); +await ogm.checkNeo4jCompat(); +---- + +== Specifying Neo4j database + +There are two ways to specify which database within a DBMS should be used. + +=== Context + +[source, javascript] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const neo4j = require("neo4j-driver"); + +const typeDefs = ` + 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, + context: { driverConfig: { database: "my-database" } } +}); +---- + +=== `Neo4jGraphQL` constructor + +[source, javascript] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const neo4j = require("neo4j-driver"); + +const typeDefs = ` + type User { + name: String + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const neoSchema = new Neo4jGraphQL({ + typeDefs, + driver, + config: { + driverConfig: { + database: "my-database", + }, + }, +}); + +const server = new ApolloServer({ + schema, +}); +---- + +== Specifying Neo4j Bookmarks + +There are two ways to specify which database bookmarks to use. + +=== Context + +[source, javascript] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const neo4j = require("neo4j-driver"); + +const typeDefs = ` + 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, + context: { driverConfig: { bookmarks: ["my-bookmark"] } } +}); +---- + +=== `Neo4jGraphQL` constructor + +[source, javascript] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const neo4j = require("neo4j-driver"); + +const typeDefs = ` + type User { + name: String + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const neoSchema = new Neo4jGraphQL({ + typeDefs, + driver, + config: { + driverConfig: { + bookmarks: ["my-bookmark"], + }, + }, +}); + +const server = new ApolloServer({ + schema, +}); +---- diff --git a/docs/asciidoc/schema/filtering.adoc b/docs/asciidoc/filtering.adoc similarity index 93% rename from docs/asciidoc/schema/filtering.adoc rename to docs/asciidoc/filtering.adoc index 8b1f2924ee..9e5e2bce15 100644 --- a/docs/asciidoc/schema/filtering.adoc +++ b/docs/asciidoc/filtering.adoc @@ -1,4 +1,4 @@ -[[schema-filtering]] +[[filtering]] = Filtering == Operators @@ -9,7 +9,7 @@ When querying for data, a number of operators are available for different types All types can be tested for either equality or non-equality. For the `Boolean` type, these are the only available comparison operators. -[[schema-filtering-numerical-operators]] +[[filtering-numerical-operators]] === Numerical operators The following comparison operators are available for numeric types (`Int`, `Float`, <>), temporal types (<>) and spatial types (<>, <>): @@ -32,6 +32,7 @@ The following case-sensitive comparison operators are only available for use on * `_CONTAINS` * `_NOT_CONTAINS` +[[filtering-regex]] ==== RegEx matching The filter `_MATCHES` is also available for comparison of `String` and `ID` types, which accepts a RegEx string as an argument and returns any matches. @@ -57,7 +58,7 @@ These four operators are available for all types apart from `Boolean`. == Usage -Using the type definitions from <>, below are some example of how filtering can be applied whilst querying for data. +Using the type definitions from <>, below are some example of how filtering can be applied whilst querying for data. === At the root of a Query diff --git a/docs/asciidoc/getting-started.adoc b/docs/asciidoc/getting-started.adoc index d6bff53064..d1c6df4166 100644 --- a/docs/asciidoc/getting-started.adoc +++ b/docs/asciidoc/getting-started.adoc @@ -1,79 +1,225 @@ [[getting-started]] = Getting Started -This section will help users get started with Neo4j GraphQL. Before starting we recommend readers have an understanding of the following **prerequisites;** +This tutorial walks you through: -1. https://developer.mozilla.org/en-US/docs/Web/JavaScript[JavaScript] -2. https://nodejs.org/en/[Node.js] -3. https://graphql.org/[GraphQL] -4. https://neo4j.com/[Neo4j] +- Installing the Neo4j GraphQL Library and its dependencies +- Defining type definitions that represent the structure of your graph database +- Instantiating an instance of the library, which will generate a GraphQL schema +- Running an instance of a server which will let you execute queries and mutations against your schema -== Installation +This tutorial assumes familiarity with the command line and JavaScript, and also that you have a recent version of Node.js installed. These examples will use the default `npm` package manager, but feel free to use your package manager of choice. + +> This tutorial walks through creating a new project with the Neo4j GraphQL Library. If you are not familiar, it will be worthwhile reading up on https://neo4j.com/[Neo4j] and https://graphql.org/[GraphQL]. + +== Step 1: Create a new project + +1. Create a new directory and `cd` into it: [source, bash] ---- -$ npm install @neo4j/graphql +mkdir neo4j-graphql-example +cd neo4j-graphql-example ---- -`graphql` and `neo4j-driver` are **peerDependencies**, so unless the project already has them installed they need to be installed as well. - -== Requiring - -The Library is transpiled, from our TypeScript source, into https://nodejs.org/docs/latest/api/modules.html#modules_modules_commonjs_modules[Common JS] - This means you can use the `require` or the `import` syntax to access the library's exported members, depending on your environment and tooling setup; +2. Create a new Node.js project: -[source, javascript] +[source, bash] ---- -const { Neo4jGraphQL } = require("@neo4j/graphql"); +npm init --yes ---- -or +Whilst we're here, let's create an empty `index.js` file which will contain all of the code for this example: -[source, javascript] +[source, bash] ---- -import { Neo4jGraphQL } from "@neo4j/graphql"; +touch index.js ---- -== GraphQL API Quick Start +== Step 2: Install dependencies + +The Neo4j GraphQL Library and it's dependencies must be installed: + +- `@neo4j/graphql` is the official Neo4j GraphQL Library package, which takes your GraphQL type definitions and generates a schema backed by a Neo4j database for you. +- `graphql` is the package used by the Neo4j GraphQL Library to generate a schema and execute queries and mutations. +- `neo4j-driver` is the official Neo4j Driver package for JavaScript, of which an instance must be passed into the Neo4j GraphQL Library. + +Additionally, we will need to install a GraphQL server package which will host our schema and allow us to execute queries and mutations against it. We will use the popular https://www.apollographql.com/docs/apollo-server/[Apollo Server] package: -This section demonstrates using https://www.apollographql.com/docs/apollo-server/[Apollo Server] alongside Neo4jGraphQL to spin up a GraphQL API. +- `apollo-server` is the default package for Apollo Server, which we will pass our generated schema into. [source, bash] ---- -$ npm install @neo4j/graphql graphql neo4j-driver apollo-server +npm install @neo4j/graphql graphql neo4j-driver apollo-server ---- +== Step 3: Define your GraphQL type definitions + +The Neo4j GraphQL Library is primarily driven by type definitions which map to the nodes and relationships in your Neo4j database. In this example, we'll use a simple example of two node labels, "Actor" and "Movie". + +Open up the previously created `index.js` in your editor of choice and write out your type definitions. We'll also add all of our necessary package imports at this stage: + [source, javascript] ---- const { Neo4jGraphQL } = require("@neo4j/graphql"); +const { ApolloServer, gql } = require("apollo-server"); const neo4j = require("neo4j-driver"); -const { ApolloServer } = require("apollo-server"); -const typeDefs = ` +const typeDefs = gql` type Movie { title: String - year: Int - imdbRating: Float - genres: [Genre] @relationship(type: "IN_GENRE", direction: OUT) + actors: [Actor] @relationship(type: "ACTED_IN", direction: IN) } - type Genre { + type Actor { name: String - movies: [Movie] @relationship(type: "IN_GENRE", direction: IN) + movies: [Movie] @relationship(type: "ACTED_IN", direction: OUT) } `; +---- +These type definitions are incredibly simple, defining the two previously described node labels, and a relationship "ACTED_IN" between the two. When generated, the schema will allow us to execute queries `actors` and `movies` to read data from the database. + +== Step 4: Create an instance of `Neo4jGraphQL` + +Now that we have our type definitions, we need to create an instance of the Neo4j GraphQL Library. To do this, we also need a Neo4j driver to connect to our database. For a database located at "bolt://localhost:7687", with a username of "neo4j" and a password of "password", add the following to the bottom of our `index.js` file: + +[source, javascript] +---- const driver = neo4j.driver( "bolt://localhost:7687", - neo4j.auth.basic("neo4j", "letmein") + neo4j.auth.basic("neo4j", "password") ); const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); +---- + +== Step 5: Create an instance of `ApolloServer` + +The final section of code we need to add is to instantiate an Apollo Server instance using our generated schema, which will allow us to execute queries against it. + +Add the following to the bottom of `index.js`: +[source, javascript] +---- const server = new ApolloServer({ schema: neoSchema.schema, - context: ({ req }) => ({ req }), }); -server.listen(4000).then(() => console.log("Online")); +server.listen().then(({ url }) => { + console.log(`🚀 Server ready at ${url}`); +}); ---- +== Step 6: Start the server + +Finally, we're ready to start up our GraphQL server! Back in the command line, run the following command: + +[source, bash] +---- +node index.js +---- + +All going well, you should see the following output: + +[source, bash] +---- +🚀 Server ready at http://localhost:4000/ +---- + +Where http://localhost:4000/ is the default URL which Apollo Server starts at. + +== Step 7: Create your first nodes in the database + +We're now ready to add some data to our Neo4j database using our GraphQL API! + +Visit http://localhost:4000/ in your web browser and you'll see the following landing page: + +image::apollo-server-landing-page.png[title="Apollo Server Landing Page"] + +Click "Query your server" which will open the Sandbox. + +image::first-mutation.png[title="First Mutation"] + +At the moment our database is empty! So let's create a movie and an actor in that movie, all in one Mutation. The Mutation in the screenshot above can also be found below: + +[source, graphql] +---- +mutation { + createMovies( + input: [ + { + title: "Forrest Gump" + actors: { create: [{ node: { name: "Tom Hanks" } }] } + } + ] + ) { + movies { + title + actors { + name + } + } + } +} +---- + +Put this Mutation into the Operations panel and hit the blue "Run" button in the top right. When you execute the Mutation, you'll receive the following response, confirmation that the data has been created in the database! + +[source, json] +---- +{ + "data": { + "createMovies": { + "movies": [ + { + "title": "Forrest Gump", + "actors": [ + { + "name": "Tom Hanks" + } + ] + } + ] + } + } +} +---- + +We can now go back and query the data which we just added: + +image::first-query.png[title="First Query"] + +The query we in the screenshot above is querying for all movies and their actors in the database: + +[source, graphql] +---- +query { + movies { + title + actors { + name + } + } +} +---- + +We of course only have the one of each, so you will see the result below: + +[source, json] +---- +{ + "data": { + "movies": [ + { + "title": "Forrest Gump", + "actors": [ + { + "name": "Tom Hanks" + } + ] + } + ] + } +} +---- diff --git a/docs/asciidoc/guides/2.0.0-migration/index.adoc b/docs/asciidoc/guides/2.0.0-migration/index.adoc index 8d04e29df6..362fac5816 100644 --- a/docs/asciidoc/guides/2.0.0-migration/index.adoc +++ b/docs/asciidoc/guides/2.0.0-migration/index.adoc @@ -1,5 +1,5 @@ -[[rel-migration]] +[[v2-migration]] = 2.0.0 Migration 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. @@ -15,4 +15,6 @@ npm update @neo4j/graphql From this point on, it is primarily Mutations which will form the bulk of the migration: -1. <> for how you need to change your Mutations to work with the new schema +1. <> for how you need to change your Mutations to work with the new schema +2. <> for how querying union fields has changed in version 2.0.0 +3. <> for other changes in version 2.0.0 diff --git a/docs/asciidoc/guides/2.0.0-migration/miscellaneous.adoc b/docs/asciidoc/guides/2.0.0-migration/miscellaneous.adoc new file mode 100644 index 0000000000..ef26214fd8 --- /dev/null +++ b/docs/asciidoc/guides/2.0.0-migration/miscellaneous.adoc @@ -0,0 +1,48 @@ +[[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 our user base will be using Apollo Client 3.0, we have updated the page-based pagination arguments to align with this change. + +For example, whilst fetching page 3 of pages of 10 movies would have looked like the following in version `1.x`: + +[source, graphql] +---- +query { + movies(options: { skip: 20, limit: 10 }) { + title + } +} +---- + +This will now need to queried as follows: + +[source, graphql] +---- +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, we have added 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] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, + config: { + skipValidateTypeDefs: true, + }, +}) +---- + +If you need to do this, please report the scenario as an issue on GitHub. diff --git a/docs/asciidoc/guides/2.0.0-migration/mutations.adoc b/docs/asciidoc/guides/2.0.0-migration/mutations.adoc index c02316bd66..2c06a25a44 100644 --- a/docs/asciidoc/guides/2.0.0-migration/mutations.adoc +++ b/docs/asciidoc/guides/2.0.0-migration/mutations.adoc @@ -1,4 +1,4 @@ -[[rel-migration-mutations]] +[[v2-migration-mutations]] = Mutations The most broadly affected area of functionality by the 2.0.0 upgrade are the nested operations of Mutations, to faciliate the mutation of and filtering on relationship properties. @@ -20,7 +20,7 @@ type Movie { 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. -[[rel-migration-mutations-create]] +[[v2-migration-mutations-create]] == Create Focussing on the `createMovies` Mutation, notice that the definition of the `createMovies` Mutation is unchanged: @@ -114,7 +114,7 @@ mutation { } ---- -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 <> for details on this. +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 <> for details on this. == Update @@ -134,9 +134,9 @@ type Mutation { } ---- -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 `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. +The `delete` nested operation is primarily the same as in the `deleteMovies` Mutation, so please see the <> section for that. === Update @@ -272,7 +272,7 @@ mutation { } ---- -[[rel-migration-mutations-delete]] +[[v2-migration-mutations-delete]] == Delete Focussing on the `deleteMovies` Mutation, notice that the definition of the `deleteMovies` Mutation is unchanged: diff --git a/docs/asciidoc/guides/2.0.0-migration/unions.adoc b/docs/asciidoc/guides/2.0.0-migration/unions.adoc index 54047b859f..1675420a6b 100644 --- a/docs/asciidoc/guides/2.0.0-migration/unions.adoc +++ b/docs/asciidoc/guides/2.0.0-migration/unions.adoc @@ -1,9 +1,9 @@ -[[rel-migration-unions]] +[[v2-migration-unions]] = Unions -The structure of input types for union queries and mutations have been changed for user friendliness, and a more consistent API. +In this release, we have decided to take the opportunity to overhaul the existing support for unions on relationship fields, to better set us up for adding top-level union support in the future. -The examples in this section will be based off the following type definitions: +All examples in this section will be based off the following type definitions: [source, graphql] ---- @@ -25,6 +25,10 @@ type Series { 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 `${unionFieldName}_${concreteType}` (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: @@ -92,3 +96,41 @@ mutation { ---- 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] +---- +query { + actors { + name + actedIn(Movie: { "The Avengers" }) { + ... on Movie { + title + } + } + } +} +---- + +This will now be written like: + +[source, graphql] +---- +query { + actors { + name + actedIn(where: { Movie: { "The Avengers" }}) { + ... on Movie { + title + } + } + } +} +---- + +We feel this has improved readability of queries. + +Furthermore, the where argument used now dictates which union members are returned from the database, to prevent overfetching. Please see <> for background and explanation of this decision. diff --git a/docs/asciidoc/guides/index.adoc b/docs/asciidoc/guides/index.adoc index 3d33558aaf..a052c2d189 100644 --- a/docs/asciidoc/guides/index.adoc +++ b/docs/asciidoc/guides/index.adoc @@ -4,4 +4,4 @@ Here you can find a selection of guides to help you work with the Neo4j GraphQL Library. 1. <> - migrating from `neo4j-graphql-js` to `@neo4j/graphql` -2. <> - migrating from version 1.* of `@neo4j/graphql` to version 2.* for relationship property support +2. <> - migrating from version 1.* of `@neo4j/graphql` to version 2.* for relationship property support diff --git a/docs/asciidoc/guides/migration-guide/mutations.adoc b/docs/asciidoc/guides/migration-guide/mutations.adoc index 4f26d4857d..739c73f222 100644 --- a/docs/asciidoc/guides/migration-guide/mutations.adoc +++ b/docs/asciidoc/guides/migration-guide/mutations.adoc @@ -26,7 +26,7 @@ A summary of migration points is as follows: * 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 <> 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! +> Note that <> 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 diff --git a/docs/asciidoc/guides/migration-guide/server.adoc b/docs/asciidoc/guides/migration-guide/server.adoc index 0b9a256126..2f1ebea082 100644 --- a/docs/asciidoc/guides/migration-guide/server.adoc +++ b/docs/asciidoc/guides/migration-guide/server.adoc @@ -84,4 +84,4 @@ server.listen().then(({ url }) => { }); ---- -Database bookmarks are also supported. See <> for more information. +Database bookmarks are also supported. See <> for more information. diff --git a/docs/asciidoc/index.adoc b/docs/asciidoc/index.adoc index 7922ddb23b..813a332375 100644 --- a/docs/asciidoc/index.adoc +++ b/docs/asciidoc/index.adoc @@ -18,44 +18,25 @@ ifdef::backend-pdf[] Documentation license: <> endif::[] - -Welcome to the official documentation for the Neo4j GraphQL Library. - -== Introduction - -In this section you will find; notes on background information, links and resources, dependencies and requirements, plus pointers on where to get support. If you are already familiar with Neo4j GraphQL, or just want to get started, then jump directly to <>. - -== Why Neo4j and GraphQL? - -Bringing the native graph storage of Neo4j to the GraphQL ecosystem has been a long time in the making. Translating your GraphQL queries to Cypher that the Neo4j database can understand means that we can easily eliminate the n+1 issue that plagues a lot of GraphQL implementations. - -== What is Neo4j GraphQL ? - -It is a GraphQL to Cypher query execution layer for Neo4j and **JavaScript** GraphQL implementations. Such an implementation makes it easier for developers to use Neo4j and GraphQL together. - -Taking a set of Type Definitions; we produce an executable GraphQL Schema, where a given GraphQL query is transformed into a single Cypher query. This translation means users can let the library handle all the database communications, and thus enabling users to focus on building great applications. - -Our GraphQL implementation exposes two products; - -1. `Neo4jGraphQL` - Used for GraphQL API's such as Apollo Server -2. <> - A Handy tool, to use in application code, driven by GraphQL Type Definitions. - -With a powerful feature set including; - -1. <> -2. Nested Mutations - -Overall anyone in the Javascript ecosystem, wanting to use either Neo4j and or GraphQL should find Neo4j GraphQL an indispensable tool for building great applications. - -== Requirements -1. https://neo4j.com/[Neo4j Database] 4.1.0 and above -2. https://neo4j.com/developer/neo4j-apoc/[APOC] 4.1.0 and above - -== Resources -1. https://github.com/neo4j/graphql[Github] -2. https://github.com/neo4j/graphql/issues[Bug Tracker] -3. https://www.npmjs.com/package/@neo4j/graphql[NPM] - -== Licence -1. Documentation: link:{common-license-page-uri}[Creative Commons 4.0] -2. Source: https://www.apache.org/licenses/LICENSE-2.0[Apache 2.0] +> This is the documentation for the Neo4j GraphQL Library version 2.0, authored by the Neo4j GraphQL Team. + +This documentation covers the following topics: + +- <> - Introduction to the Neo4j GraphQL Library. +- <> - Start here if you want to quickly get up and running with the library. +- <> - Define your nodes and relationships using type definitions as documented here. +- <> - GraphQL Queries allow you to read data in your Neo4j database. +- <> - GraphQL Mutations allow you to change data in your Neo4j database. +- <> - This chapter covers how to filter your data in Queries and Mutations. +- <> - This chapter covers how to sort the data being returned. +- <> - This chapter covers the pagination options offered by the Neo4j GraphQL Library. +- <> - Learn how to implement custom functionality accessible through your API. +- <> - Covers the authentication and authorisation options offered by this library. +- <> - An index of all of the directives offered by the Neo4j GraphQL Library. +- <> - API reference for constructing an instance of the library. +- <> - This chapter covers the OGM (Object Graph Mapper), a programmatic way of using your API. +- <> - How to configure a database driver for use with this library. +- <> - Guides for usage of the library, including migration guides for moving between versions. +- <> - Having problems with the library? See if your problem has been found and solved before. + +This manual is primarily written for software engineers building an API using the Neo4j GraphQL Library. diff --git a/docs/asciidoc/introduction.adoc b/docs/asciidoc/introduction.adoc new file mode 100644 index 0000000000..cfe4cc4036 --- /dev/null +++ b/docs/asciidoc/introduction.adoc @@ -0,0 +1,79 @@ +[[introduction]] += Introduction + +The Neo4j GraphQL Library is a highly flexible, low-code, open source JavaScript library that enables rapid API development for cross-platform and mobile applications by tapping into the power of connected data. + +With Neo4j as the graph database, the GraphQL Library makes it simple for applications to have application data treated as a graph natively from the front-end all the way to storage, avoiding duplicate schema work and ensuring flawless integration between front-end and backend developers. + +Written in TypeScript, the library's schema-first paradigm lets developers focus on the application data they need, while taking care of the heavy lifting involved in building the API. + +> Just want to get moving with the Neo4j GraphQL Library? Check out the <> guide! + +== How does it work? + +By supplying the Neo4j GraphQL Library with a set of type definitions describing the shape of your graph data, it can generate an entire executable schema with all of the additional types needed to execute queries and mutations to interact with your Neo4j database. + +For every query and mutation that is executed against this generated schema, the Neo4j GraphQL Library generates a single Cypher query which is executed against the database. This eliminates the infamous https://www.google.com/search?q=graphql+n%2B1[N+1 Problem] which can make GraphQL implementations slow and inefficient. + +== Features + +The Neo4j GraphQL Library presents a large feature set for interacting with a Neo4j database using GraphQL: + +- Automatic generation of <> and <> for CRUD interactions +- Various <>, including temporal and spatial types +- Support for both node and relationship properties +- Extensibility through the <> and/or <> +- Extensive <> and <> options +- Options for value <> and <> +- Multiple <> options +- Comprehensive authentication and authorisation options (<>), and additional <> options +- An <> (Object Graph Mapper) for programmatic interaction with your GraphQL API + +== Interaction + +In our <> guide, we start up a server using Apollo Server. This bundles Apollo Sandbox which can be used to interact directly with your GraphQL API with no front-end. + +There are a variety of front-end frameworks with clients for interacting with GraphQL APIs: + +- https://reactjs.org/[React] - support through https://www.apollographql.com/docs/react/[Apollo Client] +- https://vuejs.org/[Vue.js] - support through https://apollo.vuejs.org/[Vue Apollo] +- https://angularjs.org/[AngularJS] - support through https://apollo-angular.com/docs/[Apollo Angular] + +== Deployment + +There are a variety of methods for deploying GraphQL APIs, which we will not go into the details of in this documentation. + +However, Apollo has documented a subset in their https://www.apollographql.com/docs/apollo-server/deployment[Deployment] documentation, which will be a good starting point. + +== Versioning + +The Neo4j GraphQL Library uses https://semver.org/[Semantic Versioning]. Given a version number `MAJOR.MINOR.PATCH`, the increment is based on: + +- `MAJOR` - incompatible API changes compared to the previous `MAJOR` version, for which you will likely have to migrate. +- `MINOR` - new features have been added in a backwards compatible manner. +- `PATCH` - bug fixes have been added in a backwards compatible manner. + +Additionally, prerelease version numbers may have additional suffixes, for example `MAJOR.MINOR.PATCH-PRERELEASE.NUMBER`, where `PRERELEASE` is one of the following: + +- `alpha` - unstable prerelease artifacts, and the API may change between releases during this phase. +- `beta` - feature complete prerelease artifacts, which will be more stable than `alpha` releases but will likely still contain bugs. +- `rc` - release candidate release artifacts where each could be promoted to a stable release, in a last effort to find trailing bugs. + +`NUMBER` in the suffix is simply an incrementing release number in each phase. + +== Requirements + +1. https://neo4j.com/[Neo4j Database] 4.1.0+ +2. https://neo4j.com/developer/neo4j-apoc/[APOC] 4.1.0+ +3. https://nodejs.org/en/[Node.js] 12+ + +== Resources + +1. https://github.com/neo4j/graphql[GitHub] +2. https://github.com/neo4j/graphql/issues[Issue Tracker] +3. https://www.npmjs.com/package/@neo4j/graphql[npm package] + +== Licence + +1. Documentation: link:{common-license-page-uri}[Creative Commons 4.0] +2. Source: https://www.apache.org/licenses/LICENSE-2.0[Apache 2.0] diff --git a/docs/asciidoc/mutations.adoc b/docs/asciidoc/mutations.adoc deleted file mode 100644 index 222d77f889..0000000000 --- a/docs/asciidoc/mutations.adoc +++ /dev/null @@ -1,4 +0,0 @@ -[[mutations]] -= Mutations - -See <>. diff --git a/docs/asciidoc/mutations/create.adoc b/docs/asciidoc/mutations/create.adoc new file mode 100644 index 0000000000..af059dc8c0 --- /dev/null +++ b/docs/asciidoc/mutations/create.adoc @@ -0,0 +1,98 @@ +[[mutations-create]] += Create + +Using the following type definitions for these examples: + +[source, graphql] +---- +type Post { + id: ID! @id + content: String! + creator: User @relationship(type: "HAS_POST", direction: IN) +} + +type User { + id: ID! @id + name: String + posts: [Post] @relationship(type: "HAS_POST", direction: OUT) +} +---- + +The following create Mutations and response types will be generated for the above type definitions: + +[source, graphql] +---- +type CreatePostsMutationResponse { + posts: [Post!]! +} + +type CreateUsersMutationResponse { + users: [User!]! +} + +type Mutation { + createPosts(input: [PostCreateInput!]!): CreatePostsMutationResponse! + createUsers(input: [UsersCreateInput!]!): CreateUsersMutationResponse! +} +---- + +The `CreateInput` types closely mirror the object type definitions, allowing you to create not only the type in question, but to recurse down and perform further operations on related types in the same Mutation. + +> The `id` field will be absent from both create input types as the <> directive has been used. + +== Single create + +A single user can be created by executing the following GraphQL statement: + +[source, graphql] +---- +mutation { + createUsers(input: [ + { + name: "John Doe" + } + ]) { + users { + id + name + } + } +} +---- + +This will create a User with name "John Doe", and that name plus the autogenerated ID will be returned. + +== Nested create + +A User and an initial Post can be created by executing the following: + +[source, graphql] +---- +mutation { + createUsers(input: [ + { + name: "John Doe" + posts: { + create: [ + { + node: { + content: "Hi, my name is John!" + } + } + ] + } + } + ]) { + users { + id + name + posts { + id + content + } + } + } +} +---- + +This will create a User with name "John Doe", an introductory Post, both of which will be returned with their autogenerated IDs. diff --git a/docs/asciidoc/mutations/delete.adoc b/docs/asciidoc/mutations/delete.adoc new file mode 100644 index 0000000000..1f60ba99f9 --- /dev/null +++ b/docs/asciidoc/mutations/delete.adoc @@ -0,0 +1,114 @@ +[[mutations-delete]] += Delete + +Using the following type definitions for these examples: + +[source, graphql] +---- +type Post { + id: ID! @id + content: String! + creator: User @relationship(type: "HAS_POST", direction: IN) +} + +type User { + id: ID! @id + name: String + posts: [Post] @relationship(type: "HAS_POST", direction: OUT) +} +---- + +The following delete Mutations and response type will be generated for the above type definitions: + +[source, graphql] +---- +type DeleteInfo { + nodesDeleted: Int! + relationshipsDeleted: Int! +} + +type Mutation { + deletePosts(where: PostWhere, delete: PostDeleteInput): DeleteInfo! + deleteUsers(where: UserWhere, delete: UserDeleteInput): DeleteInfo! +} +---- + +Note that the `DeleteInfo` type is the common return type for all delete Mutations. + +== Single Delete + +A single post can be deleted by executing the following GraphQL statement: + +[source, graphql] +---- +mutation { + deletePosts(where: [ + { + id: "6042E807-47AE-4857-B7FE-1AADF522DE8B" + } + ]) { + nodesDeleted + relationshipsDeleted + } +} +---- + +This will delete the post using the autogenerated ID that would have been returned after that post's creation. + +We would see that `nodesDeleted` would equal 1 (the post) and `relationshipsDeleted` would also equal equal 1 (the `HAS_POST` relationship between the Post and its author). + +== Nested Delete + +Say that if when we delete a User, we want to delete _all_ of their Posts as well. This can be achieved using a single nested delete operations: + +[source, graphql] +---- +mutation { + deleteUsers( + where: [ + { + name: "Jane Doe" + } + ], + delete: { + posts: [ + where: { } + ] + } + ) { + nodesDeleted + relationshipsDeleted + } +} +---- + +You may look at that empty `where` argument and wonder what that's doing. By the time we reach that argument, we are already only dealing with the posts that were created by Jane Doe, as we traversed the graph to those Post nodes from her User node. Essentially, the above query is equivalent to: + +[source, graphql] +---- +mutation { + deleteUsers( + where: [ + { + name: "Jane Doe" + } + ], + delete: { + posts: [ + where: { + node: { + creator: { + name: "Jane Doe" + } + } + } + ] + } + ) { + nodesDeleted + relationshipsDeleted + } +} +---- + +Slightly easier to reason with, but the output Cypher statement will have a redundant `WHERE` clause! diff --git a/docs/asciidoc/mutations/index.adoc b/docs/asciidoc/mutations/index.adoc new file mode 100644 index 0000000000..c3929b36c3 --- /dev/null +++ b/docs/asciidoc/mutations/index.adoc @@ -0,0 +1,18 @@ +[[mutations]] += Mutations + +Several Mutations are automatically generated for each type defined in type definitions, which are covered in the following chapters: + +- <> - create nodes, and recursively create or connect further nodes in the graph +- <> - update nodes, and recursively perform any operations from there +- <> - delete nodes, and recursively delete or disconnect further nodes in the graph + +== A note on nested Mutations + +You will see some basic examples of nested Mutations in this chapter, which barely scratch the surface of what can be achieved with them. We really encourage you to explore the power of what you can do with them! + +However, it has to be noted that in order to provide the abstractions available in these Mutations, the output Cypher can end up being extremely complex, which can result in your database throwing out-of-memory errors depending on its configuration. + +If out-of-memory errors are a regular occurrence, you can adjust the `dbms.memory.heap.max_size` parameter in the DBMS settings. + +If you need to perform major data migrations, it may be best to manually write the necessary Cypher and execute this directly in the database. diff --git a/docs/asciidoc/mutations/update.adoc b/docs/asciidoc/mutations/update.adoc new file mode 100644 index 0000000000..1776d86b5b --- /dev/null +++ b/docs/asciidoc/mutations/update.adoc @@ -0,0 +1,101 @@ +[[mutations-update]] += Update + +Using the following type definitions for these examples: + +[source, graphql] +---- +type Post { + id: ID! @id + content: String! + creator: User @relationship(type: "HAS_POST", direction: IN) +} + +type User { + id: ID! @id + name: String + posts: [Post] @relationship(type: "HAS_POST", direction: OUT) +} +---- + +The following update Mutations and response types will be generated for the above type definitions: + +[source, graphql] +---- +type UpdatePostsMutationResponse { + posts: [Post!]! +} + +type UpdateUsersMutationResponse { + users: [User!]! +} + +type Mutation { + updatePosts( + where: PostWhere + update: PostUpdateInput + connect: PostConnectInput + disconnect: PostDisconnectInput + create: PostCreateInput + delete: PostDeleteInput + ): UpdatePostsMutationResponse! + updateUsers( + where: UserWhere + update: UserUpdateInput + connect: UserConnectInput + disconnect: UserDisconnectInput + create: UserCreateInput + delete: UserDeleteInput + ): UpdateUsersMutationResponse! +} +---- + +> The `id` field not be update-able as the <> directive has been used. + +== Single update + +Say we wanted to edit the content of a Post: + +[source, graphql] +---- +mutation { + updatePosts( + where: { + id: "892CC104-A228-4BB3-8640-6ADC9F2C2A5F" + } + update: { + content: "Some new content for this Post!" + } + ) { + posts { + content + } + } +} +---- + +== Nested create + +Instead of creating a Post and connecting it to a User, you could update a User and create a Post as part of the Mutation: + +[source, graphql] +---- +mutation { + updateUsers( + where: { name: "John Doe" } + create: { + posts: [ + { node: { content: "An interesting way of adding a new Post!" } } + ] + } + ) { + users { + id + name + posts { + content + } + } + } +} +---- diff --git a/docs/asciidoc/ogm/api-reference.adoc b/docs/asciidoc/ogm/api-reference.adoc deleted file mode 100644 index cabcfa5d4f..0000000000 --- a/docs/asciidoc/ogm/api-reference.adoc +++ /dev/null @@ -1,62 +0,0 @@ -[[ogm-api-reference]] -= API Reference - -[[ogm-api-reference-ogm]] -== `OGM` - -=== Requiring -[source, javascript] ----- -const { OGM } = require("@neo4j/graphql-ogm"); ----- - -=== Constructing - -[source, javascript] ----- -const ogm = new OGM({ - typeDefs, - resolvers?, -}); ----- - -=== Methods - -==== `model()` -Reference: <> - -[[ogm-api-reference-model]] -== `Model` - -=== Requiring -[source, typescript] ----- -import type { Model } from "@neo4j/graphql-ogm" ----- - -=== Constructing - -You construct a model from invoking the `.model` method on an <>. - -[source, javascript] ----- -const model = ogm.model("name") ----- - -=== Methods - -==== `find()` -Reference: <> - -==== `count()` -Reference: <> - -==== `create()` -Reference: <> - -==== `update()` -Reference: <> - -==== `delete()` -Reference: <> - diff --git a/docs/asciidoc/ogm/api-reference/index.adoc b/docs/asciidoc/ogm/api-reference/index.adoc new file mode 100644 index 0000000000..1c6371241c --- /dev/null +++ b/docs/asciidoc/ogm/api-reference/index.adoc @@ -0,0 +1,5 @@ +[[ogm-api-reference]] += API Reference + +- <> +- <> diff --git a/docs/asciidoc/ogm/api-reference/model/count.adoc b/docs/asciidoc/ogm/api-reference/model/count.adoc new file mode 100644 index 0000000000..43b4d6c099 --- /dev/null +++ b/docs/asciidoc/ogm/api-reference/model/count.adoc @@ -0,0 +1,35 @@ +[[ogm-api-reference-model-count]] += `count` + +Returns a `Promise` that resolvers to the count of nodes based on the arguments passed in. + +== Example + +To query for all User nodes: + +[source, javascript] +---- +const User = ogm.model("User"); + +const usersCount = await User.count(); +---- + +To query for User nodes where name starts with the letter "D": + +[source, javascript] +---- +const User = ogm.model("User"); + +const usersCount = await User.count({ where: { name_STARTS_WITH: "D" }}); +---- + +== Arguments + +|=== +|Name and Type |Description + +|`where` + + + + Type: `GraphQLWhereArg` +|A JavaScript object representation of the GraphQL `where` input type used for <>. +|=== diff --git a/docs/asciidoc/ogm/api-reference/model/create.adoc b/docs/asciidoc/ogm/api-reference/model/create.adoc new file mode 100644 index 0000000000..dc4acf19ec --- /dev/null +++ b/docs/asciidoc/ogm/api-reference/model/create.adoc @@ -0,0 +1,48 @@ +[[ogm-api-reference-model-create]] += `create` + +This method can be used to update nodes, and maps to the underlying <> Mutation. + +Returns a `Promise` that resolves to the equivalent of the Mutation response for this operation. + +== Example + +To create a Movie with title "The Matrix": + +[source, javascript] +---- +const Movie = ogm.model("Movie"); + +await Movie.create({ input: [{ title: "The Matrix" }] }) +---- + +== Arguments + +|=== +|Name and Type |Description + +|`input` + + + + Type: `any` +|JavaScript object representation of the GraphQL `input` input type used for <> mutations. + +|`selectionSet` + + + + Type: `string` or `DocumentNode` or `SelectionSetNode` +|Selection set for the Mutation, see <> for more information. + +|`args` + + + + Type: `any` +|The `args` value for the GraphQL Mutation. + +|`context` + + + + Type: `any` +|The `context` value for the GraphQL Mutation. + +|`rootValue` + + + + Type: `any` +|The `rootValue` value for the GraphQL Mutation. +|=== diff --git a/docs/asciidoc/ogm/api-reference/model/delete.adoc b/docs/asciidoc/ogm/api-reference/model/delete.adoc new file mode 100644 index 0000000000..ecca36d2d4 --- /dev/null +++ b/docs/asciidoc/ogm/api-reference/model/delete.adoc @@ -0,0 +1,57 @@ +[[ogm-api-reference-model-delete]] += `delete` + +This method can be used to delete nodes, and maps to the underlying <> Mutation. + +Returns a `Promise` which resolvers to a `DeleteInfo` object: + +|=== +|Name and Type |Description + +|`nodesDeleted` + + + + Type: `number` +|The number of nodes deleted. + +|`relationshipsDeleted` + + + + Type: `number` +|The number of relationships deleted. +|=== + +== Example + +To delete all User nodes where the name is "Dan": + +[source, javascript] +---- +const User = ogm.model("User"); + +await User.delete({ where: { name: "Dan" }}); +---- + +== Arguments + +|=== +|Name and Type |Description + +|`where` + + + + Type: `GraphQLWhereArg` +|A JavaScript object representation of the GraphQL `where` input type used for <>. + +|`delete` + + + + Type: `string` or `DocumentNode` or `SelectionSetNode` +|A JavaScript object representation of the GraphQL `delete` input type used for <> Mutations. + +|`context` + + + + Type: `any` +|The `context` value for the GraphQL Mutation. + +|`rootValue` + + + + Type: `any` +|The `rootValue` value for the GraphQL Mutation. +|=== diff --git a/docs/asciidoc/ogm/api-reference/model/find.adoc b/docs/asciidoc/ogm/api-reference/model/find.adoc new file mode 100644 index 0000000000..5484a9604e --- /dev/null +++ b/docs/asciidoc/ogm/api-reference/model/find.adoc @@ -0,0 +1,62 @@ +[[ogm-api-reference-model-find]] += `find` + +This method can be used to find nodes, and maps to the underlying schema <>. + +Returns a `Promise` which resolvers to an array of objects matching the type of the Model. + +== Example + +To find all user nodes in the database: + +[source, javascript] +---- +const User = ogm.model("User"); + +const users = await User.find(); +---- + +To find users with name "Jane Smith": + +[source, javascript] +---- +const User = ogm.model("User"); + +const users = await User.find({ where: { name: "Jane Smith" }}); +---- + +== Arguments + +|=== +|Name and Type |Description + +|`where` + + + + Type: `GraphQLWhereArg` +|A JavaScript object representation of the GraphQL `where` input type used for <>. + +|`options` + + + + Type: `GraphQLOptionsArg` +|A JavaScript object representation of the GraphQL `options` input type used for <> and <>. + +|`selectionSet` + + + + Type: `string` or `DocumentNode` or `SelectionSetNode` +|Selection set for the Mutation, see <> for more information. + +|`args` + + + + Type: `any` +|The `args` value for the GraphQL Mutation. + +|`context` + + + + Type: `any` +|The `context` value for the GraphQL Mutation. + +|`rootValue` + + + + Type: `any` +|The `rootValue` value for the GraphQL Mutation. +|=== diff --git a/docs/asciidoc/ogm/api-reference/model/index.adoc b/docs/asciidoc/ogm/api-reference/model/index.adoc new file mode 100644 index 0000000000..552c97c272 --- /dev/null +++ b/docs/asciidoc/ogm/api-reference/model/index.adoc @@ -0,0 +1,22 @@ +[[ogm-api-reference-model]] += `Model` + +== `create` + +See <>. + +== `find` + +See <>. + +== `update` + +See <>. + +== `delete` + +See <>. + +== `count` + +See <>. diff --git a/docs/asciidoc/ogm/api-reference/model/update.adoc b/docs/asciidoc/ogm/api-reference/model/update.adoc new file mode 100644 index 0000000000..b35a2c2edb --- /dev/null +++ b/docs/asciidoc/ogm/api-reference/model/update.adoc @@ -0,0 +1,76 @@ +[[ogm-api-reference-model-update]] += `update` + +This method can be used to update nodes, and maps to the underlying <> Mutation. + +Returns a `Promise` that resolves to the equivalent of the Mutation response for this operation. + +== Example + +For the User with name "John", update their name to be "Jane": + +[source, javascript] +---- +const User = ogm.model("User"); + +const { users } = await User.update({ + where: { name: "John" }, + update: { name: "Jane" }, +}); +---- + +== Arguments + +|=== +|Name and Type |Description + +|`where` + + + + Type: `GraphQLWhereArg` +|A JavaScript object representation of the GraphQL `where` input type used for <>. + +|`update` + + + + Type: `any` +|A JavaScript object representation of the GraphQL `update` input type used for <> Mutations. + +|`connect` + + + + Type: `any` +|A JavaScript object representation of the GraphQL `connect` input type used for <> Mutations. + +|`disconnect` + + + + Type: `any` +|A JavaScript object representation of the GraphQL `disconnect` input type used for <> Mutations. + +|`create` + + + + Type: `any` +|A JavaScript object representation of the GraphQL `create` input type used for <> Mutations. + +|`options` + + + + Type: `GraphQLOptionsArg` +|A JavaScript object representation of the GraphQL `options` input type used for <> and <>. + +|`selectionSet` + + + + Type: `string` or `DocumentNode` or `SelectionSetNode` +|Selection set for the Mutation, see <> for more information. + +|`args` + + + + Type: `any` +|The `args` value for the GraphQL Mutation. + +|`context` + + + + Type: `any` +|The `context` value for the GraphQL Mutation. + +|`rootValue` + + + + Type: `any` +|The `rootValue` value for the GraphQL Mutation. +|=== diff --git a/docs/asciidoc/ogm/api-reference/ogm.adoc b/docs/asciidoc/ogm/api-reference/ogm.adoc new file mode 100644 index 0000000000..73e761fafd --- /dev/null +++ b/docs/asciidoc/ogm/api-reference/ogm.adoc @@ -0,0 +1,48 @@ +[[ogm-api-reference-ogm]] += `OGM` + +== `constructor` + +Returns an `OGM` instance. + +Takes an `input` object as a parameter, which is then passed to the `Neo4jGraphQL` constructor. Supported options are listed in the documentation for <>. + +=== Example + +[source, javascript] +---- +const ogm = new OGM({ + typeDefs, +}); +---- + +== `model` + +Returns a `Model` instance matching the passed in name, or throws an `Error` if one can't be found. + +Accepts a single argument `name` of type `string`. + +=== Example + +For the following type definitions: + +[source, graphql] +---- +type User { + username: String! +} +---- + +The following would successfully return a `Model` instance: + +[source, javascript] +---- +const User = ogm.model("User"); +---- + +The following would throw an `Error`: + +[source, javascript] +---- +const User = ogm.model("NotFound"); +---- diff --git a/docs/asciidoc/ogm/examples/custom-resolvers.adoc b/docs/asciidoc/ogm/examples/custom-resolvers.adoc new file mode 100644 index 0000000000..1a38c1ca6e --- /dev/null +++ b/docs/asciidoc/ogm/examples/custom-resolvers.adoc @@ -0,0 +1,137 @@ +[[ogm-examples-custom-resolvers]] += Custom Resolvers + +A common case for using the OGM will be within custom resolvers inside a Neo4j GraphQL instance (very meta!), due to the fact that it has access to some fields which the Neo4j GraphQL Library may not. A common use case might be to have a `password` field marked with directive `@private`, and a custom resolver for creating users with passwords. + +To get started with this example, create your example application directory, create a new project and also the file which will contain our application code: + +[source, bash] +---- +mkdir ogm-custom-resolvers-example +cd ogm-custom-resolvers-example +npm init --yes +touch index.js +---- + +Then we need to install our dependencies: + +[source, bash] +---- +npm install @neo4j/graphql-ogm graphql neo4j-driver apollo-server +---- + +Assuming a running Neo4j database at "bolt://localhost:7687" with username "neo4j" and password "password", in our empty `index.js` file, add the following code: + +[source, javascript] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const { OGM } = require("@neo4j/graphql-ogm"); +const { ApolloServer } = require("apollo-server"); +const neo4j = require("neo4j-driver"); + +const { createJWT, comparePassword } = require("./utils"); // example util functions + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const typeDefs = ` + type User { + id: ID @id + username: String! + password: String! @private + } + + type Mutation { + signUp(username: String!, password: String!): String! ### JWT + signIn(username: String!, password: String!): String! ### JWT + } +`; + +const ogm = new OGM({ typeDefs, driver }); +const User = ogm.model("User"); + +const resolvers = { + Mutation: { + signUp: async (_source, { username, password }) => { + const [existing] = await User.find({ + where: { + username, + }, + }); + + if (existing) { + throw new Error(`User with username ${username} already exists!`); + } + + const [user] = await User.create({ + input: [ + { + username, + password, + } + ] + }); + + return createJWT({ sub: user.id }); + }, + signIn: async (_source, { email, password }) => { + const [user] = await User.find({ + where: { + username, + }, + }); + + if (!user) { + throw new Error(`User with username ${username} not found!`); + } + + const correctPassword = await comparePassword(password, user.password); + + if (!correctPassword) { + throw new Error(`Incorrect password for user with username ${username}!`); + } + + return createJWT({ sub: user.id }); + }, + }, +}; + +const neoSchema = new Neo4jGraphQL({ + typeDefs, + resolvers, + config: { + jwt: { + secret: "secret", + }, + }, +}); + +const server = new ApolloServer({ + schema: neoSchema.schema, + context: ({ req }) => ({ req }), +}); + +server.listen().then(({ url }) => { + console.log(`🚀 Server ready at ${url}`); +}); +---- + +It's important to note the JWT secret being passed into the `Neo4jGraphQL` constructor in this example. + +Back in the command line, run the following command to start your server: + +[source, bash] +---- +node index.js +---- + +You should see the following output: + +[source, bash] +---- +🚀 Server ready at http://localhost:4000/ +---- + +You can execute the `signUp` Mutation against this GraphQL API to sign up, but when you go to query the user through the same API, the password field will not be available. diff --git a/docs/asciidoc/ogm/examples/index.adoc b/docs/asciidoc/ogm/examples/index.adoc new file mode 100644 index 0000000000..0f6c42c589 --- /dev/null +++ b/docs/asciidoc/ogm/examples/index.adoc @@ -0,0 +1,7 @@ +[[ogm-examples]] += Examples + +This chapter runs through some examples of how you might take advantage of the OGM. + +- <> - using the OGM in custom resolvers within your Neo4j GraphQL API +- <> - exposing your Neo4j GraphQL API through a REST API, using the OGM diff --git a/docs/asciidoc/ogm/examples/rest-api.adoc b/docs/asciidoc/ogm/examples/rest-api.adoc new file mode 100644 index 0000000000..1eaf426705 --- /dev/null +++ b/docs/asciidoc/ogm/examples/rest-api.adoc @@ -0,0 +1,85 @@ +[[ogm-examples-rest-api]] += REST API + +This example demonstrates how you might use the OGM without exposing a Neo4j GraphQL API endpoint. The example starts an https://expressjs.com/[Express] server and uses the OGM to interact with the Neo4j GraphQL Library, exposed over a REST endpoint. + +First, create your example application directory, create a new project and also the file which will contain our application code: + +[source, bash] +---- +mkdir ogm-rest-example +cd ogm-rest-example +npm init --yes +touch index.js +---- + +Then we need to install our dependencies: + +[source, bash] +---- +npm install @neo4j/graphql-ogm graphql neo4j-driver express +---- + +Assuming a running Neo4j database at "bolt://localhost:7687" with username "neo4j" and password "password", in our empty `index.js` file, add the following code: + +[source, javascript] +---- +const express = require("express"); +const { OGM } = require("@neo4j/graphql-ogm"); +const neo4j = require("neo4j-driver"); + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const typeDefs = ` + type User { + id: ID + name: String + } +`; + +const ogm = new OGM({ typeDefs, driver }); +const User = ogm.model("User"); + +const app = express(); + +app.get("/users", async (req, res) => { + const { search, offset, limit, sort } = req.query; + + const regex = search ? `(?i).*${search}.*` : null; + + const users = await User.find({ + where: { name_REGEX: regex }, + options: { + offset, + limit, + sort + } + }); + + return res.json(users).end(); +}); + +const port = 4000; +app.listen(port, () => { + console.log("Example app listening at http://localhost:${port}") +}); +---- + +In our application directory, you can run this application: + +[source, bash] +---- +node index.js +---- + +Which should output: + +[source, bash] +---- +Example app listening at http://localhost:4000 +---- + +The REST API should now be ready to accept requests at the URL logged. diff --git a/docs/asciidoc/ogm/getting-started.adoc b/docs/asciidoc/ogm/getting-started.adoc deleted file mode 100644 index 8603065a6c..0000000000 --- a/docs/asciidoc/ogm/getting-started.adoc +++ /dev/null @@ -1,66 +0,0 @@ -[[ogm-getting-started]] -= Getting Started - -This section will help you get started with Neo4j GraphQL OGM. Before starting we recommend readers have an understanding of; - -* `Neo4jGraphQL` <> -* <> -* <> - -== Installation -[source, bash] ----- -npm install @neo4j/graphql-ogm ----- - -graphql and neo4j-driver are are peerDependencies. - -== Requiring -[source, javascript] ----- -const { OGM } = require("@neo4j/graphql-ogm"); ----- - -== REST API Quick Start -This section demonstrates how to use the OGM outside of a GraphQL API. The example exposes a https://expressjs.com/[Express] server and uses the OGM, in the endpoint, to interact with Neo4j. - -[source, javascript] ----- -const express = require("express"); -const { OGM } = require("@neo4j/graphql-ogm"); -const neo4j = require("neo4j-driver"); - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("neo4j", "letmein") -); - -const typeDefs = ` - type User { - id: ID - name: String - } -`; - -const ogm = new OGM({ typeDefs, driver }); -const User = ogm.model("User"); - -const app = express(); -app.get("/users", async (req, res) => { - const { search, offset, limit, sort } = req.query; - - const regex = search ? `(?i).*${search}.*` : null; - - const users = await User.find({ - where: { name_REGEX: regex }, - options: { - offset, - limit, - sort - } - }); - - return res.json(users).end(); -}); -app.listen(4000, () => console.log("started")); ----- diff --git a/docs/asciidoc/ogm/index.adoc b/docs/asciidoc/ogm/index.adoc index 45e200770d..e63ea42d1a 100644 --- a/docs/asciidoc/ogm/index.adoc +++ b/docs/asciidoc/ogm/index.adoc @@ -1,22 +1,27 @@ [[ogm]] = OGM -Common applications won't just expose a single API. On the same instance as the GraphQL API there may be; scheduled jobs, authentication, migrations and not to forget any custom logic in the resolvers themselves. We expose an OGM (Object Graph Mapper) on top of the pre-existing GraphQL work and abstractions. +Most applications won't just expose a single GraphQL API. There may also be scheduled jobs, authentication and migrations keeping an application ticking over. We provide an OGM (Object Graph Mapper) which can be used to programmatically interact with your Neo4j GraphQL API, which may help with achieving these goals. -* <> -* <> -* <> -* <> -* <> +- <> +- <> +- <> +- <> +- <> +- <> +> Before diving into the OGM, it's important to have a good understanding of the Neo4h GraphQL Library first. It's recommended to at least work through the <> guide. -== Directives -The following directives are excluded from the OGM's schema; +== Excluded directives -* `@auth` -* `@exclude` -* `@private` -* `@readonly` -* `@writeonly` +The following directives are excluded from the OGM's schema: + +- `@auth` +- `@exclude` +- `@private` +- `@readonly` +- `@writeonly` + +This is because the OGM is only ever used programmatically, as opposed to an exposed API which needs these security measures. See also: <> diff --git a/docs/asciidoc/ogm/installation.adoc b/docs/asciidoc/ogm/installation.adoc new file mode 100644 index 0000000000..5ff063f873 --- /dev/null +++ b/docs/asciidoc/ogm/installation.adoc @@ -0,0 +1,22 @@ +[[ogm-installation]] += Installation + +The OGM is very easy to install into a new or existing Node.js project. However it does have a couple of dependencies. The OGM depends on the Neo4j GraphQL Library, which will be installed when you install the OGM, so you will require the following dependencies: + +- `@neo4j/graphql-ogm` is the OGM package. +- `graphql` is the package used by the Neo4j GraphQL Library to generate a schema and execute queries and mutations. +- `neo4j-driver` is the official Neo4j Driver package for JavaScript, necessary for interacting with the database. + +[source, bash] +---- +npm install @neo4j/graphql-ogm graphql neo4j-driver +---- + +To use the OGM, it will need to be imported wherever you want to use it: + +[source, javascript] +---- +const { OGM } = require("@neo4j/graphql-ogm"); +---- + +It's recommended to check out the <> to see where you might go from here. diff --git a/docs/asciidoc/ogm/methods/count.adoc b/docs/asciidoc/ogm/methods/count.adoc deleted file mode 100644 index 3d9cd8688d..0000000000 --- a/docs/asciidoc/ogm/methods/count.adoc +++ /dev/null @@ -1,20 +0,0 @@ -[[ogm-methods-count]] -= Count - -Use to count nodes. - -== Usage - -=== Basic - -[source, javascript] ----- -const User = ogm.model("User"); - -const usersCount = await User.count(); ----- - -== Args - -=== `where` -JavaScript object representation of the GraphQL `where` input type, used for <>. diff --git a/docs/asciidoc/ogm/methods/create.adoc b/docs/asciidoc/ogm/methods/create.adoc deleted file mode 100644 index db71e8eaba..0000000000 --- a/docs/asciidoc/ogm/methods/create.adoc +++ /dev/null @@ -1,30 +0,0 @@ -[[ogm-methods-create]] -= Create - -Use to create many nodes. The `model.create` method maps to the underlying <> operation. - -== Usage -[source, javascript] ----- -const Movie = ogm.model("Movie"); - -await Movie.create({ input: [{ title: "The Matrix" }] }) ----- - -== Args - -=== `input` -JavaScript object representation of the GraphQL `input` input type, used for <>. - -=== `selectionSet` - -Reference: <> - -=== `args` -The arguments to the GraphQL Query. - -=== `context` -The `context` for the GraphQL Query. - -=== `rootValue` -The `rootValue` for the GraphQL Query. diff --git a/docs/asciidoc/ogm/methods/delete.adoc b/docs/asciidoc/ogm/methods/delete.adoc deleted file mode 100644 index e566fb4b43..0000000000 --- a/docs/asciidoc/ogm/methods/delete.adoc +++ /dev/null @@ -1,29 +0,0 @@ -[[ogm-methods-delete]] -= Delete - -Use to delete many nodes. The `model.delete` method maps to the underlying <> operation. - -== Usage -[source, javascript] ----- -const User = ogm.model("User"); - -await User.delete({ where: { name: "dan" }}); ----- - -== Args - -=== `where` -JavaScript object representation of the GraphQL `where` input type, used for <>. - -=== `delete` -JavaScript object representation of the GraphQL `delete` input type, used for <>. - -=== `args` -The arguments for the GraphQL Query. - -=== `context` -The `context` for the GraphQL Query. - -=== `rootValue` -The `rootValue` for the GraphQL Query. diff --git a/docs/asciidoc/ogm/methods/find.adoc b/docs/asciidoc/ogm/methods/find.adoc deleted file mode 100644 index 5af3383653..0000000000 --- a/docs/asciidoc/ogm/methods/find.adoc +++ /dev/null @@ -1,56 +0,0 @@ -[[ogm-methods-find]] -= Find - -Use to update many nodes. The `model.find` method maps to the underlying schema <>. - -== Usage - -=== Basic - -[source, javascript] ----- -const User = ogm.model("User"); - -const users = await User.find(); ----- - -== Relationships -Reference: <> - -[source, javascript] ----- -const User = ogm.model("User"); - -const selectionSet = ` - { - posts { - content - } - } -`; - -const users = await User.find({ - selectionSet, -}); ----- - -== Args - -=== `where` -JavaScript object representation of the GraphQL `where` input type, used for <>. - -=== `options` -JavaScript object representation of the GraphQL `options` input type, used for <>. - -=== `selectionSet` - -Reference: <> - -=== `args` -The arguments for the GraphQL Query. - -=== `context` -The `context` for the GraphQL Query. - -=== `rootValue` -The `rootValue` for the GraphQL Query. diff --git a/docs/asciidoc/ogm/methods/index.adoc b/docs/asciidoc/ogm/methods/index.adoc deleted file mode 100644 index 42a02f1bdf..0000000000 --- a/docs/asciidoc/ogm/methods/index.adoc +++ /dev/null @@ -1,12 +0,0 @@ -[[ogm-methods]] -= Methods - -You can call the following on a model; - -. <> -. <> -. <> -. <> -. <> - -Each method maps to the underlying generated Query or Mutation for that Model. diff --git a/docs/asciidoc/ogm/methods/update.adoc b/docs/asciidoc/ogm/methods/update.adoc deleted file mode 100644 index 46b7213099..0000000000 --- a/docs/asciidoc/ogm/methods/update.adoc +++ /dev/null @@ -1,45 +0,0 @@ -[[ogm-methods-update]] -= Update - -Use to update many nodes. The `model.update` method maps to the underlying <> operation. - -== Usage -[source, javascript] ----- -const User = ogm.model("User"); - -const { users } = await User.update({ - where: { name: "bob" }, - update: { name: "Bob" }, -}); ----- - -== Args - -=== `where` -JavaScript object representation of the GraphQL `where` input type, used for <>. - -=== `update` -JavaScript object representation of the GraphQL `update` input type, used for <>. - -=== `connect` -JavaScript object representation of the GraphQL `connect` input type, used for <>. - -=== `disconnect` -JavaScript object representation of the GraphQL `disconnect` input type, used for <>. - -=== `create` -JavaScript object representation of the GraphQL `create` input type, used for <>. - -=== `selectionSet` - -Reference: <> - -=== `args` -The arguments for the GraphQL Query. - -=== `context` -The `context` for the GraphQL Query. - -=== `rootValue` -The `rootValue` for the GraphQL Query. \ No newline at end of file diff --git a/docs/asciidoc/ogm/selection-set.adoc b/docs/asciidoc/ogm/selection-set.adoc index 979e7c4250..354f3fdbbc 100644 --- a/docs/asciidoc/ogm/selection-set.adoc +++ b/docs/asciidoc/ogm/selection-set.adoc @@ -1,7 +1,7 @@ [[ogm-selection-set]] = Selection Set -This is a GraphQL specific term. When you preform a query you have the operation; +This is a GraphQL specific term. When you execute a query, you have the operation: [source, graphql] ---- @@ -10,32 +10,41 @@ query { } ---- -And you also have a Selection Set; +And you also have a selection set. For example, from the example below: [source, graphql] ---- query { myOperation { - # Selection Set start - id - name - } # Selection Set end + field1 + field2 + } +} +---- + +The following snippet is the selection set: + +[source, graphql] +---- +{ + field1 + field2 } ---- -When using the OGM we do not want users providing a selections sets. Doing so would make the OGM feel more like querying the GraphQL Schema when the OGM is designed as an abstraction on top of it. To combat this we do Autogenerated Selection Sets. Given a Node; +When using the OGM we do not want users to have to provide a selection set by default. Doing so would make using the OGM feel more like querying the GraphQL schema directly, when the OGM is designed as an abstraction over it. To combat this, we automatically generate basic selection sets. Given the following type definition: [source, graphql] ---- type Node { id: ID name: String - relation: [Node] @relationship(...) - customCypher: [Node] @cypher(...) + relatedNodes: [Node] @relationship(type: "HAS_NODE", direction: OUT) + customCypher: String! @cypher(statement: "RETURN someCustomData") } ---- -We pre-generate a pre-defined selection set. We don't include any relationships or cypher fields, as they could be computationally expensive. Given the above Node the auto pre-defined selection set would be; +We don't include any relationship fields or Cypher fields in the generated selection set, as they could be computationally expensive. So, given the type definition above, the generated selection set would be: [source, graphql] ---- @@ -45,9 +54,11 @@ We pre-generate a pre-defined selection set. We don't include any relationships } ---- -This means that by default, querying for Node(s), you would only get the `.id` and `.name` properties returned. If you want to select more you can either define a selection set at execution time or as a static on the Model; +This means that by default, querying for Node(s), you would only get the `.id` and `.name` properties returned. If you want to select more fields, you can either define a selection set at execution time or as a static on the Model, as described below. + +== Selection set at execution time -=== Selection set at execution time +Using this approach, we would pass in a selection set every time we interact with the OGM. This would be an appropriate approach if the selection set is going to be different every time you ask for data. A full example of this would be as follows: [source, javascript] ---- @@ -63,8 +74,8 @@ const typeDefs = ` type Node { id: ID name: String - relation: [Node] @relationship(...) - customCypher: [Node] @cypher(...) + relatedNodes: [Node] @relationship(type: "HAS_NODE", direction: OUT) + customCypher: String! @cypher(statement: "RETURN someCustomData") } `; @@ -75,20 +86,22 @@ const selectionSet = ` { id name - relation { - id - name - } - customCypher { + relatedNodes { id name } + customCypher } `; + const nodes = await Node.find({ selectionSet }); ---- -=== Selection set as a static +Note that we are passing in the argument `selectionSet` into the `Node.find()` function. + +== Selection set as a static + +Using this approach, you can assign a selection set to a particular Model, so that whenever it is queried, it will always return those fields. This is useful if the default selection set doesn't quite give you enough data, but you don't need the selection set to be dynamic on each request. See a full example below: [source, javascript] ---- @@ -104,8 +117,8 @@ const typeDefs = ` type Node { id: ID name: String - relation: [Node] @relationship(...) - customCypher: [Node] @cypher(...) + relatedNodes: [Node] @relationship(type: "HAS_NODE", direction: OUT) + customCypher: String! @cypher(statement: "RETURN someCustomData") } `; @@ -116,15 +129,17 @@ const selectionSet = ` { id name - relation { - id - name - } - customCypher { + relatedNodes { id name } + customCypher } `; + Node.setSelectionSet(selectionSet) + +const nodes = await Node.find(); ---- + +Note that despite not passing this selection set into `Node.find()`, the requested fields will be returned on each request. diff --git a/docs/asciidoc/pagination/cursor-based.adoc b/docs/asciidoc/pagination/cursor-based.adoc new file mode 100644 index 0000000000..d0fd2fe06b --- /dev/null +++ b/docs/asciidoc/pagination/cursor-based.adoc @@ -0,0 +1,89 @@ +[[pagination-cursor-based]] += Cursor-based pagination + +On relationship fields, you are able to take advantage of cursor-based pagination, which is often associated with infinitely-scrolling applications. + +Using the following type definition: + +[source, graphql] +---- +type User { + name: String! + posts: [Post!]! @relationship(type: "HAS_POST", direction: OUT) +} + +type Post { + content: String! +} +---- + +If you wanted to fetch the posts of user "John Smith" 10 at a time, you would first fetch 10: + +[source, graphql] +---- +query { + users(where: { name: "John Smith" }) { + name + postsConnection(first: 10) { + edges { + node { + content + } + } + pageInfo { + endCursor + hasNextPage + } + } + } +} +---- + +In the return value, if `hasNextPage` is `true`, you would pass `endCursor` into the next query of 10. We would likely do this using a variable as in the following example: + +[source, graphql] +---- +query Users($after: String) { + users(where: { name: "John Smith" }) { + name + postsConnection(first: 10, after: $after) { + edges { + node { + content + } + } + pageInfo { + endCursor + hasNextPage + } + } + } +} +---- + +You would continue doing this until `hasNextPage` if `false` - this is when you have reached the end of the data. + +== `totalCount` + +The Connection fields also offer a `totalCount` field which can be used to calculate page numbers, which is useful if you want to page by cursors but use page number in your application. Using the example above, you would simply add the `totalCount` field which will return the total number of results matching the filter used, which in this example would just be all posts: + +[source, graphql] +---- +query Users($after: String) { + users(where: { name: "John Smith" }) { + name + postsConnection(first: 10) { + edges { + node { + content + } + } + pageInfo { + endCursor + hasNextPage + } + totalCount + } + } +} +---- diff --git a/docs/asciidoc/pagination/index.adoc b/docs/asciidoc/pagination/index.adoc new file mode 100644 index 0000000000..a4b09e1e28 --- /dev/null +++ b/docs/asciidoc/pagination/index.adoc @@ -0,0 +1,7 @@ +[[pagination]] += Pagination + +The Neo4j GraphQL Library offers two mechanisms for pagination: + +- <> - Pagination based on offsets, often associated with navigation via pages. +- <> - Pagination based on cursors, often associated with infinitely-scrolling applications. diff --git a/docs/asciidoc/schema/pagination.adoc b/docs/asciidoc/pagination/offset-based.adoc similarity index 59% rename from docs/asciidoc/schema/pagination.adoc rename to docs/asciidoc/pagination/offset-based.adoc index f3954b1d62..564849b1a5 100644 --- a/docs/asciidoc/schema/pagination.adoc +++ b/docs/asciidoc/pagination/offset-based.adoc @@ -1,9 +1,7 @@ -[[schema-pagination]] -= Pagination +[[pagination-offset-based]] += Offset-based pagination -== Page-based pagination - -Page-based pagination can be achieved through the use of the `offset` and `limit` options available whilst querying for data. +Offset-based pagination, often associated with navigation via pages, can be achieved through the use of the `offset` and `limit` options available whilst querying for data. Using the following type definition: @@ -29,7 +27,8 @@ query { And then on subsequent calls, introduce the `offset` argument and increment it by 10 on each call. -Page 2: +*Page 2:* + [source, graphql] ---- query { @@ -42,7 +41,8 @@ query { } ---- -Page 3: +*Page 3:* + [source, graphql] ---- query { @@ -55,7 +55,15 @@ query { } ---- -=== Paginating relationship fields +And so on, so forth. + +== Total number of pages + +You can fetch the total number of records for a certain type using its count query, and then divide that number by your entries per page in order to calculate the total number of pages. This will allow to to determine what the last page is, and whether there is a next page. + +See <> queries for details on how to execute these queries. + +== Paginating relationship fields Say that in addition to the `User` type above, there is also a `Post` type which a `User` has many of. You can also fetch a `User` and then paginate through their posts: diff --git a/docs/asciidoc/queries.adoc b/docs/asciidoc/queries.adoc index 83dd9e7109..5a74c75f05 100644 --- a/docs/asciidoc/queries.adoc +++ b/docs/asciidoc/queries.adoc @@ -1,4 +1,96 @@ [[queries]] = Queries -See <>. +Each node defined in type definitions will have two Query fields generated for it - one for querying data and one for counting data. + +The examples in this chapter will use the following type definitions: + +Given the following type definitions: + +[source, graphql] +---- +type Post { + id: ID! @id + content: String! + creator: User! @relationship(type: "HAS_POST", direction: IN) +} + +type User { + id: ID! @id + name: String! + posts: [Post!]! @relationship(type: "HAS_POST", direction: OUT) +} +---- + +For which the following Query fields will be generated: + +[source, graphql] +---- +type Query { + posts(where: PostWhere, options: PostOptions): [Post!]! + postsCount(where: PostWhere): Int! + users(where: UserWhere, options: UserOptions): [User!]! + usersCount(where: UserWhere): Int! +} +---- + +== Query + +Each field for querying data accepts two arguments: + +- `where` - used for <> data +- `options` - used to specify <> and <> options + +=== Querying for all User nodes + +The following Query will return all User nodes, returning their ID and name. + +[source, graphql] +---- +query { + users { + id + name + } +} +---- + +=== Query for user with name "Jane Smith" and their posts + +The following Query will return all Users, returning the content which they have posted. + +[source, graphql] +---- +query { + users(where: { name: "Jane Smith" }) { + id + name + posts { + content + } + } +} +---- + +[[queries-count]] +== Count + +=== Counting all User nodes + +The following Query will count all User nodes: + +[source, graphql] +---- +query { + usersCount +} +---- + +=== Counting User nodes where name starts with "J" + +[source, graphql] +---- +query { + usersCount(where: { name_STARTS_WITH: "J" }) +} +---- diff --git a/docs/asciidoc/schema/index.adoc b/docs/asciidoc/schema/index.adoc deleted file mode 100644 index 433eceed0f..0000000000 --- a/docs/asciidoc/schema/index.adoc +++ /dev/null @@ -1,10 +0,0 @@ -[[schema]] -= Schema - -In this section you will learn how to use the exposed GraphQL schema to interact with the Neo4j database. - -* <> -* <> -* <> -* <> -* <> diff --git a/docs/asciidoc/schema/mutations.adoc b/docs/asciidoc/schema/mutations.adoc deleted file mode 100644 index 8d00c3b6b6..0000000000 --- a/docs/asciidoc/schema/mutations.adoc +++ /dev/null @@ -1,294 +0,0 @@ -[[schema-mutations]] -= Mutations - -Mutations for create, update and delete operations are automatically generated for each type defined in type definitions. - -This section will briefly go through each Mutation generated, using the following type definitions as an example: - -[source, graphql] ----- -type Post { - id: ID! @id - content: String! - creator: User @relationship(type: "HAS_POST", direction: IN) -} - -type User { - id: ID! @id - name: String - posts: [Post] @relationship(type: "HAS_POST", direction: OUT) -} ----- - -*A note on nested Mutations* - -> You will see some basic examples of nested Mutations below, which barely scratch the surface of what can be achieved with them. We really encourage you to explore the power of what you can do with them! -> -> However, it has to be noted that in order to provide the abstractions available in these Mutations, the output Cypher can end up being extremely complex, which can result in your database throwing out-of-memory errors depending on its configuration. -> -> If out-of-memory errors are a regular occurrence, you can adjust the `dbms.memory.heap.max_size` parameter in the DBMS settings. -> -> If you need to perform major data migrations, it may be best to manually write the necessary Cypher and execute this directly in the database. - -[[schema-mutations-create]] -== Create - -The following create Mutations and response types will be generated for the above type definitions: - -[source, graphql] ----- -type CreatePostsMutationResponse { - posts: [Post!]! -} - -type CreateUsersMutationResponse { - users: [User!]! -} - -type Mutation { - createPosts(input: [PostCreateInput!]!): CreatePostsMutationResponse! - createUsers(input: [UsersCreateInput!]!): CreateUsersMutationResponse! -} ----- - -The `CreateInput` types closely mirror the object type definitions, allowing you to create not only the type in question, but to recurse down and perform further operations on related types in the same Mutation. - -> The `id` field will be absent from both create input types as the <> directive has been used. - -=== Single create - -A single user can be created by executing the following GraphQL statement: - -[source, graphql] ----- -mutation { - createUsers(input: [ - { - name: "John Doe" - } - ]) { - users { - id - name - } - } -} ----- - -This will create a User with name "John Doe", and that name plus the autogenerated ID will be returned. - -=== Nested create - -A User and an initial Post can be created by executing the following: - -[source, graphql] ----- -mutation { - createUsers(input: [ - { - name: "John Doe" - posts: { - create: [ - { - node: { - content: "Hi, my name is John!" - } - } - ] - } - } - ]) { - users { - id - name - posts { - id - content - } - } - } -} ----- - -This will create a User with name "John Doe", an introductory Post, both of which will be returned with their autogenerated IDs. - -[[schema-mutations-update]] -== Update - -[source, graphql] ----- -type UpdatePostsMutationResponse { - posts: [Post!]! -} - -type UpdateUsersMutationResponse { - users: [User!]! -} - -type Mutation { - updatePosts( - where: PostWhere - update: PostUpdateInput - connect: PostConnectInput - disconnect: PostDisconnectInput - create: PostCreateInput - delete: PostDeleteInput - ): UpdatePostsMutationResponse! - updateUsers( - where: UserWhere - update: UserUpdateInput - connect: UserConnectInput - disconnect: UserDisconnectInput - create: UserCreateInput - delete: UserDeleteInput - ): UpdateUsersMutationResponse! -} ----- - -> The `id` field not be update-able as the <> directive has been used. - -=== Single update - -Say we wanted to edit the content of a Post: - -[source, graphql] ----- -mutation { - updatePosts( - where: { - id: "892CC104-A228-4BB3-8640-6ADC9F2C2A5F" - } - update: { - content: "Some new content for this Post!" - } - ) { - posts { - content - } - } -} ----- - -=== Nested update - -Instead of creating a Post and connecting it to a User, you could update a User and create a Post as part of the Mutation: - -[source, graphql] ----- -mutation { - updateUsers( - where: { name: "John Doe" } - create: { - posts: [ - { node: { content: "An interesting way of adding a new Post!" } } - ] - } - ) { - users { - id - name - posts { - content - } - } - } -} ----- - -[[schema-mutations-delete]] -== Delete - -The following delete Mutations and response type will be generated for the above type definitions: - -[source, graphql] ----- -type DeleteInfo { - nodesDeleted: Int! - relationshipsDeleted: Int! -} - -type Mutation { - deletePosts(where: PostWhere, delete: PostDeleteInput): DeleteInfo! - deleteUsers(where: UserWhere, delete: UserDeleteInput): DeleteInfo! -} ----- - -Note that the `DeleteInfo` type is the common return type for all delete Mutations. - -=== Single Delete - -A single post can be deleted by executing the following GraphQL statement: - -[source, graphql] ----- -mutation { - deletePosts(where: [ - { - id: "6042E807-47AE-4857-B7FE-1AADF522DE8B" - } - ]) { - nodesDeleted - relationshipsDeleted - } -} ----- - -This will delete the post using the autogenerated ID that would have been returned after that post's creation. - -We would see that `nodesDeleted` would equal 1 (the post) and `relationshipsDeleted` would also equal equal 1 (the `HAS_POST` relationship between the Post and its author). - -=== Nested Delete - -Say that if when we delete a User, we want to delete _all_ of their Posts as well. This can be achieved using a single nested delete operations: - -[source, graphql] ----- -mutation { - deleteUsers( - where: [ - { - name: "Jane Doe" - } - ], - delete: { - posts: [ - where: { } - ] - } - ) { - nodesDeleted - relationshipsDeleted - } -} ----- - -You may look at that empty `where` argument and wonder what that's doing. By the time we reach that argument, we are already only dealing with the posts that were created by Jane Doe, as we traversed the graph to those Post nodes from her User node. Essentially, the above query is equivalent to: - -[source, graphql] ----- -mutation { - deleteUsers( - where: [ - { - name: "Jane Doe" - } - ], - delete: { - posts: [ - where: { - node: { - creator: { - name: "Jane Doe" - } - } - } - ] - } - ) { - nodesDeleted - relationshipsDeleted - } -} ----- - -Slightly easier to reason with, but the output Cypher statement will have a redundant `WHERE` clause! diff --git a/docs/asciidoc/schema/queries.adoc b/docs/asciidoc/schema/queries.adoc deleted file mode 100644 index e11f9023b3..0000000000 --- a/docs/asciidoc/schema/queries.adoc +++ /dev/null @@ -1,83 +0,0 @@ -[[schema-queries]] -= Queries - -Each object defined in type definitions will have a Query field generated for it. Each Query field accepts two arguments: - -* `where`: Used to specify the <> which should be applied whilst querying the data -* `options`: Used to specify the options for <> and <> - -== Example - -Given the following type definitions: - -[source, graphql] ----- -type Post { - id: ID! @id - content: String! - creator: User @relationship(type: "HAS_POST", direction: IN) -} - -type User { - id: ID! @id - name: String - posts: [Post] @relationship(type: "HAS_POST", direction: OUT) -} ----- - -The following Query fields will be automatically generated: - -[source, graphql] ----- -type Query { - posts(where: PostWhere, options: PostOptions): [Post!]! - users(where: UserWhere, options: UserOptions): [User!]! - countPosts(where: PostWhere): Int! - usersCount(where: UserWhere): Int! -} ----- - -=== Querying for all Users - -The following Query will return all Users, returning their ID and name. - -[source, graphql] ----- -query { - users { - id - name - } -} ----- - -=== Query for all Users' Posts - -The following Query will return all Users, returning the content which they have posted. - -[source, graphql] ----- -query { - users { - posts { - content - } - } -} ----- - - -=== Counting all Users - -The following Query will count all users and return a number. - -[source, graphql] ----- -query { - usersCount -} ----- - -=== Filtering - -See <> for details on how data can be filtered whilst querying. diff --git a/docs/asciidoc/schema/sorting.adoc b/docs/asciidoc/sorting.adoc similarity index 98% rename from docs/asciidoc/schema/sorting.adoc rename to docs/asciidoc/sorting.adoc index 2e7147f561..b623542b3a 100644 --- a/docs/asciidoc/schema/sorting.adoc +++ b/docs/asciidoc/sorting.adoc @@ -1,4 +1,4 @@ -[[schema-sorting]] +[[sorting]] = Sorting A sorting input type is generated for every object type defined in your type definitions, allowing for Query results to be sorted by each individual field. diff --git a/docs/asciidoc/troubleshooting/faqs.adoc b/docs/asciidoc/troubleshooting/faqs.adoc new file mode 100644 index 0000000000..36b539a67b --- /dev/null +++ b/docs/asciidoc/troubleshooting/faqs.adoc @@ -0,0 +1,16 @@ +[[troubleshooting-faqs]] += FAQs + +This chapter contains commonly asked questions and their solutions. + +== I've upgraded from <1.1.0 and my `DateTime` fields aren't sorting as expected + +Due to a bug in versions less than 1.1.0, there is a chance that your `DateTime` fields are stored in the database as strings instead of temporal values. You should perform a rewrite of those properties in your database using a Cypher query. For an example where the affected node has label "Movie" and the affected property is "timestamp", you can do this using the following Cypher: + +[source, javascript] +---- +MATCH (m:Movie) +WHERE apoc.meta.type(m.timestamp) = "STRING" +SET m.timestamp = datetime(m.timestamp) +RETURN m +---- diff --git a/docs/asciidoc/troubleshooting.adoc b/docs/asciidoc/troubleshooting/index.adoc similarity index 89% rename from docs/asciidoc/troubleshooting.adoc rename to docs/asciidoc/troubleshooting/index.adoc index 1abe182c9c..82d88b2c77 100644 --- a/docs/asciidoc/troubleshooting.adoc +++ b/docs/asciidoc/troubleshooting/index.adoc @@ -1,6 +1,8 @@ [[troubleshooting]] = Troubleshooting +This chapter contains common troubleshooting steps. Additionally, there is a section for <> (Frequently Asked Questions) where you might find answers to your problems. + == Debug Logging `@neo4j/graphql` uses the https://www.npmjs.com/package/debug[`debug`] library for debug-level logging. You can turn on all debug logging by setting the environment variable `DEBUG` to `@neo4j/graphql:*` whilst running. For example: @@ -17,6 +19,7 @@ Alternatively, if you are debugging a particular functionality, you can specify 3. `@neo4j/graphql:graphql` - Logs the GraphQL query and variables 4. `@neo4j/graphql:execute` - Logs the Cypher and Cypher paramaters before execution, and summary of execution +[[troubleshooting-query-tuning]] == Query Tuning Hopefully you won't need to perform any query tuning, but if you do, the Neo4j GraphQL Library allows you to set the full array of query options on construction of the library. diff --git a/docs/asciidoc/type-definitions/basics.adoc b/docs/asciidoc/type-definitions/basics.adoc new file mode 100644 index 0000000000..38eee04413 --- /dev/null +++ b/docs/asciidoc/type-definitions/basics.adoc @@ -0,0 +1,61 @@ +[[type-definitions-basics]] += Basics + +Each type in your GraphQL type definitions can be mapped to an entity in your Neo4j database. + +== Nodes + +The most basic mapping is of GraphQL types to Neo4j nodes, where the GraphQL type name maps to the Neo4j node label. + +For example, to represent a node with label "Movie" and a single property "title" of type string: + +[source, graphql] +---- +type Movie { + title: String +} +---- + +== Relationships + +Relationships are represented by marking particular fields with a directive. This directive, `@relationship`, defines the relationship type in the database, as well as which direction that relationship goes in. + +Let's add a second node type, "Actor", and connect the two together: + +[source, graphql] +---- +type Movie { + title: String + actors: [Actor] @relationship(type: "ACTED_IN", direction: IN) +} + +type Actor { + name: String + movies: [Movie] @relationship(type: "ACTED_IN", direction: OUT) +} +---- + +Note there is a directive on each "end" of the relationship in this case, but it is not essential. + +=== Relationship properties + +In order to add relationship properties to a relationship, we need to add a new type to our type definitions, but this time it will be of type `interface`. For example, for our "ACTED_IN" relationship, let's add a property "roles": + +[source, graphql] +---- +type Movie { + title: String + actors: [Actor] @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") +} + +type Actor { + name: String + movies: [Movie] @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedIn") +} + +interface ActedIn { + roles: [String] +} +---- + +Note that in addition to this new interface type, we have added a key `properties` to the existing `@relationship` directives. diff --git a/docs/asciidoc/type-definitions/cypher.adoc b/docs/asciidoc/type-definitions/cypher.adoc index 90d570624d..8b4be795c1 100644 --- a/docs/asciidoc/type-definitions/cypher.adoc +++ b/docs/asciidoc/type-definitions/cypher.adoc @@ -1,5 +1,5 @@ [[type-definitions-cypher]] -= `@cypher` Directive += `@cypher` directive The `@cypher` directive binds a GraphQL field to the result(s) of a Cypher query. diff --git a/docs/asciidoc/type-definitions/index.adoc b/docs/asciidoc/type-definitions/index.adoc index 93eb1127b8..ed02870ebe 100644 --- a/docs/asciidoc/type-definitions/index.adoc +++ b/docs/asciidoc/type-definitions/index.adoc @@ -1,36 +1,14 @@ [[type-definitions]] = Type Definitions -* <> -* <> -* <> -* <> -* <> +- <> - Learn how to define your nodes and relationships using GraphQL type definitions. +- <> - Learn about the various data types available in the Neo4j GraphQL Library. +- <> - Learn about two GraphQL concepts, unions and interfaces, and how they map to the Neo4j database. +- <> - Learn more about defining relationships using the Neo4j GraphQL Library. +- <> - Learn about how to restrict access to certain types or fields. +- <> - Learn about certain types which you can enable autogeneration of values for. +- <> - Learn about how to add custom Cypher to your type definitions. +- <> - Learn about different ways of setting default values for particular fields. -== Basics - -=== Nodes - -To represent a node in the GraphQL schema use the type definition; - -[source, graphql] ----- -type Node { - id: ID -} ----- - - -=== Relationships - -To represent a relationship between two nodes use the `@relationship` directive; - -[source, graphql] ----- -type Node { - id: ID - related: [Node] @relationship(type: "RELATED", direction: OUT) -} ----- diff --git a/docs/asciidoc/type-definitions/types.adoc b/docs/asciidoc/type-definitions/types.adoc index b077b97889..35fe2000eb 100644 --- a/docs/asciidoc/type-definitions/types.adoc +++ b/docs/asciidoc/type-definitions/types.adoc @@ -1,33 +1,32 @@ [[type-definitions-types]] = Types -Neo4j GraphQL supports all of the default GraphQL https://graphql.org/learn/schema/#scalar-types[scalar types] as well as the additional scalar and object types specified in this document. +Neo4j GraphQL supports all of the default GraphQL https://graphql.org/learn/schema/#scalar-types[scalar types] as well as additional scalar and object types specific to the Neo4j database. -[[type-definitions-types-datetime]] -== `DateTime` -ISO datetime string stored as a https://neo4j.com/docs/cypher-manual/current/functions/temporal/#functions-datetime[datetime] temporal type. +== Int -[source, graphql] ----- -type User { - createdAt: DateTime -} ----- +One of the default GraphQL scalar types. Supports up to 53-bit values - see <> for 64-bit value support. -[[type-definitions-types-date]] -== `Date` -"yyyy-mm-dd" date string stored as a https://neo4j.com/docs/cypher-manual/current/functions/temporal/#functions-date[date] temporal type. +== Float -[source, graphql] ----- -type Movie { - releaseDate: Date -} ----- +One of the default GraphQL scalar types. + +== String + +One of the default GraphQL scalar types. + +== Boolean + +One of the default GraphQL scalar types. + +== ID + +One of the default GraphQL scalar types. Stored as a string in the database and always returned as a string. [[type-definitions-types-bigint]] == `BigInt` -Supports up to 64 bit integers, serialized as strings in variables and in data responses. Shares the same <> as the other numeric types. + +Supports up to 64 bit integers, serialized as strings in variables and in data responses. Shares the same <> as the other numeric types. [source, graphql] ---- @@ -47,6 +46,32 @@ query { } ---- +== Temporal Types + +[[type-definitions-types-datetime]] +=== `DateTime` + +ISO datetime string stored as a https://neo4j.com/docs/cypher-manual/current/functions/temporal/#functions-datetime[datetime] temporal type. + +[source, graphql] +---- +type User { + createdAt: DateTime +} +---- + +[[type-definitions-types-date]] +=== `Date` + +"yyyy-mm-dd" date string stored as a https://neo4j.com/docs/cypher-manual/current/functions/temporal/#functions-date[date] temporal type. + +[source, graphql] +---- +type Movie { + releaseDate: Date +} +---- + [[type-definitions-types-spatial-types]] == Spatial Types @@ -132,7 +157,7 @@ mutation CreateUsers($name: String!, $longitude: Float!, $latitude: Float!) { ==== Filtering -In addition to the <>, the `Point` type has an additional `_DISTANCE` filter. All of the filters take the following type as an argument: +In addition to the <>, the `Point` type has an additional `_DISTANCE` filter. All of the filters take the following type as an argument: [source, graphql] ---- @@ -209,7 +234,7 @@ input CartesianPointInput { ==== Filtering -In addition to the <>, the `CartesianPoint` type has an additional `_DISTANCE` filter. All of the filters take the following type as an argument: +In addition to the <>, the `CartesianPoint` type has an additional `_DISTANCE` filter. All of the filters take the following type as an argument: [source, graphql] ---- diff --git a/docs/asciidoc/type-definitions/unions-and-interfaces.adoc b/docs/asciidoc/type-definitions/unions-and-interfaces.adoc index 1980e23cab..5b2ab9a94c 100644 --- a/docs/asciidoc/type-definitions/unions-and-interfaces.adoc +++ b/docs/asciidoc/type-definitions/unions-and-interfaces.adoc @@ -29,6 +29,7 @@ type User { Below you can find some examples of how queries and mutations work with this example. +[[type-definitions-unions-and-interfaces-union-types-querying]] === Querying a union Which union members are returned by a Query are dictated by the `where` filter applied. @@ -155,4 +156,6 @@ mutation CreateUserAndContent { == Interface Types -Using interface types will give you no database support, therefore there is no support for querying, updating, deleting, filtering. But instead used as a language feature to safeguard your schema. Great for when dealing with repetitive or large schemas you can essentially put "The side railings up". +At the moment, the only support that the Neo4j GraphQL Library offers for interfaces is in the definition of relationship properties. + +Beyond this, feel free to use them in your implementation of node types, however the library will offer no database support for these - it will essentially ignore them and focus only on the implementing GraphQL object types. diff --git a/docs/docbook/content-map.xml b/docs/docbook/content-map.xml index 11c0a4eb6c..c84b5f4c07 100644 --- a/docs/docbook/content-map.xml +++ b/docs/docbook/content-map.xml @@ -3,12 +3,19 @@ + + + + + + + @@ -32,35 +39,47 @@ - - + + - - - - - - - - - - + + + + + + + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + @@ -68,6 +87,9 @@ + + + @@ -76,18 +98,18 @@ - - - - - - + + + + + + @@ -97,44 +119,67 @@ + + + + + + - - + + + - - - - - - - - - - - - - + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + @@ -154,16 +199,25 @@ - - - - + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/docs/images/apollo-server-landing-page.png b/docs/images/apollo-server-landing-page.png new file mode 100644 index 0000000000..f4af49d883 Binary files /dev/null and b/docs/images/apollo-server-landing-page.png differ diff --git a/docs/images/first-mutation.png b/docs/images/first-mutation.png new file mode 100644 index 0000000000..a223eee3bd Binary files /dev/null and b/docs/images/first-mutation.png differ diff --git a/docs/images/first-query.png b/docs/images/first-query.png new file mode 100644 index 0000000000..30f09bb66a Binary files /dev/null and b/docs/images/first-query.png differ diff --git a/packages/graphql/package.json b/packages/graphql/package.json index 2997acba09..bd68d9d085 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -44,7 +44,7 @@ "@types/node": "14.0.27", "@types/pluralize": "0.0.29", "@types/randomstring": "1.1.6", - "apollo-server": "2.21.0", + "apollo-server": "^3.0.2", "dedent": "^0.7.0", "faker": "5.2.0", "graphql-tag": "2.11.0", diff --git a/yarn.lock b/yarn.lock index 5996f80f4d..c281894005 100644 --- a/yarn.lock +++ b/yarn.lock @@ -35,6 +35,30 @@ __metadata: languageName: node linkType: hard +"@apollo/protobufjs@npm:1.2.2": + version: 1.2.2 + resolution: "@apollo/protobufjs@npm:1.2.2" + dependencies: + "@protobufjs/aspromise": ^1.1.2 + "@protobufjs/base64": ^1.1.2 + "@protobufjs/codegen": ^2.0.4 + "@protobufjs/eventemitter": ^1.1.0 + "@protobufjs/fetch": ^1.1.0 + "@protobufjs/float": ^1.0.2 + "@protobufjs/inquire": ^1.1.0 + "@protobufjs/path": ^1.1.2 + "@protobufjs/pool": ^1.1.0 + "@protobufjs/utf8": ^1.1.0 + "@types/long": ^4.0.0 + "@types/node": ^10.1.0 + long: ^4.0.0 + bin: + apollo-pbjs: bin/pbjs + apollo-pbts: bin/pbts + checksum: 75940f213d39728f2fdd662732eb6c085fb393bea01ccb58c5084a18559d1d7342bac3ebb44f93c9e86b8237c675ae81a04427866d156f93b16b691d020bfd44 + languageName: node + linkType: hard + "@apollo/protobufjs@npm:^1.0.3": version: 1.2.0 resolution: "@apollo/protobufjs@npm:1.2.0" @@ -77,6 +101,13 @@ __metadata: languageName: node linkType: hard +"@apollographql/apollo-tools@npm:^0.5.1": + version: 0.5.1 + resolution: "@apollographql/apollo-tools@npm:0.5.1" + checksum: f06b922b829167298ccfedd60bf9a918c537ceb83299a27d9e3369fe6009638cf58f3ed16ca3d88c94ec5b6e20867820c07f464d5666516d8d32a96fb82685c4 + languageName: node + linkType: hard + "@apollographql/graphql-playground-html@npm:1.6.26": version: 1.6.26 resolution: "@apollographql/graphql-playground-html@npm:1.6.26" @@ -95,6 +126,15 @@ __metadata: languageName: node linkType: hard +"@apollographql/graphql-playground-html@npm:1.6.29": + version: 1.6.29 + resolution: "@apollographql/graphql-playground-html@npm:1.6.29" + dependencies: + xss: ^1.0.8 + checksum: ea45ba54c1110f21f5bfc5751d29802346165568f47431ebcf9411164bb98ea551dab0fd5cc95bf688be98f0df5414811b04a0b794621782db9be20d0cf60939 + languageName: node + linkType: hard + "@apollographql/graphql-upload-8-fork@npm:^8.1.3": version: 8.1.3 resolution: "@apollographql/graphql-upload-8-fork@npm:8.1.3" @@ -650,7 +690,7 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/utils@npm:^7.10.0": +"@graphql-tools/utils@npm:^7.10.0, @graphql-tools/utils@npm:^7.9.0": version: 7.10.0 resolution: "@graphql-tools/utils@npm:7.10.0" dependencies: @@ -964,7 +1004,7 @@ __metadata: "@types/node": 14.0.27 "@types/pluralize": 0.0.29 "@types/randomstring": 1.1.6 - apollo-server: 2.21.0 + apollo-server: ^3.0.2 camelcase: ^6.2.0 debug: ^4.3.2 dedent: ^0.7.0 @@ -1291,6 +1331,16 @@ __metadata: languageName: node linkType: hard +"@types/body-parser@npm:1.19.1": + version: 1.19.1 + resolution: "@types/body-parser@npm:1.19.1" + dependencies: + "@types/connect": "*" + "@types/node": "*" + checksum: abbebf715ffda345ee9ffc53f4fdad033b63cffcae1e22ffe58c6c8dc251f4879efd6d06714171c106ea89ad3175d9d70021fdc470bf1f0234eb334f3922770e + languageName: node + linkType: hard + "@types/classnames@npm:^2.2.10": version: 2.3.0 resolution: "@types/classnames@npm:2.3.0" @@ -1335,6 +1385,13 @@ __metadata: languageName: node linkType: hard +"@types/cors@npm:2.8.12": + version: 2.8.12 + resolution: "@types/cors@npm:2.8.12" + checksum: f3b62196df42c286282b9dbda26e24d2264211d932e7d9c2240f361e27ef693a5cb03790daeb3610fd958a0690f4d31d45ef7848b0df7a0dd2afc698a4f9df76 + languageName: node + linkType: hard + "@types/cors@npm:2.8.8": version: 2.8.8 resolution: "@types/cors@npm:2.8.8" @@ -1423,6 +1480,17 @@ __metadata: languageName: node linkType: hard +"@types/express-serve-static-core@npm:4.17.24": + version: 4.17.24 + resolution: "@types/express-serve-static-core@npm:4.17.24" + dependencies: + "@types/node": "*" + "@types/qs": "*" + "@types/range-parser": "*" + checksum: 30fc9b62a7cc52fa320fbdf11510b20d91c1aa55d99d90ecb51708b9178aa670f2aaa94eed01f68a96a1a80b3575f1143c0cb23b0e69d60070d6d6d42e40b62a + languageName: node + linkType: hard + "@types/express@npm:*, @types/express@npm:4.17.11": version: 4.17.11 resolution: "@types/express@npm:4.17.11" @@ -1435,6 +1503,18 @@ __metadata: languageName: node linkType: hard +"@types/express@npm:4.17.13": + version: 4.17.13 + resolution: "@types/express@npm:4.17.13" + dependencies: + "@types/body-parser": "*" + "@types/express-serve-static-core": ^4.17.18 + "@types/qs": "*" + "@types/serve-static": "*" + checksum: 9f17da703df21e3f1cee2fe1864b9fcac2ab07c37382b972a194a3a484b41c1fbe4022b6cfe546f0171fd2d93b324dd3839512494f4cba639c2afa021e6dbb12 + languageName: node + linkType: hard + "@types/express@npm:4.17.7": version: 4.17.7 resolution: "@types/express@npm:4.17.7" @@ -2856,6 +2936,16 @@ __metadata: languageName: node linkType: hard +"apollo-datasource@npm:^3.0.3": + version: 3.0.3 + resolution: "apollo-datasource@npm:3.0.3" + dependencies: + apollo-server-caching: ^3.0.1 + apollo-server-env: ^4.0.3 + checksum: fb3729cdc85169899c99444e109ae4b671be68752d4d1d02de8d8fe66577c958d6accf8502e129098745e157d2168a63d69513be2fa6bb1e4a13c199c11a2008 + languageName: node + linkType: hard + "apollo-env@npm:^0.6.6": version: 0.6.6 resolution: "apollo-env@npm:0.6.6" @@ -2892,6 +2982,19 @@ __metadata: languageName: node linkType: hard +"apollo-graphql@npm:^0.9.0": + version: 0.9.3 + resolution: "apollo-graphql@npm:0.9.3" + dependencies: + core-js-pure: ^3.10.2 + lodash.sortby: ^4.7.0 + sha.js: ^2.4.11 + peerDependencies: + graphql: ^14.2.1 || ^15.0.0 + checksum: 0da8498289a5a6f7b586d7a4336ec2219069c0136b70b3480079ac6cb357a94a53a1e6a411f0a18e1752f6644d5935e3627e67be9b88da5eb7d1731d8513e783 + languageName: node + linkType: hard + "apollo-link-context@npm:1.0.20": version: 1.0.20 resolution: "apollo-link-context@npm:1.0.20" @@ -2962,6 +3065,15 @@ __metadata: languageName: node linkType: hard +"apollo-reporting-protobuf@npm:^3.0.0": + version: 3.0.0 + resolution: "apollo-reporting-protobuf@npm:3.0.0" + dependencies: + "@apollo/protobufjs": 1.2.2 + checksum: fec8965cac06753090a7625e7c0f06db136e1680dd6c28a92d961222f3c3c65764d559eda44a1088c8bf50f168b757131e36960dfda265b82e282d6ff6e26dfd + languageName: node + linkType: hard + "apollo-server-caching@npm:^0.5.3": version: 0.5.3 resolution: "apollo-server-caching@npm:0.5.3" @@ -2980,7 +3092,16 @@ __metadata: languageName: node linkType: hard -"apollo-server-core@npm:^2.19.0, apollo-server-core@npm:^2.21.0, apollo-server-core@npm:^2.23.0": +"apollo-server-caching@npm:^3.0.1": + version: 3.0.1 + resolution: "apollo-server-caching@npm:3.0.1" + dependencies: + lru-cache: ^6.0.0 + checksum: 0fd19a71bf017ba31de9b9fa751f141407986cdaaad423cc265186bfb51e0f6cfef53e391172dd470b03fc68f5e302b059942bcf012b497aaf18337814c2c4b7 + languageName: node + linkType: hard + +"apollo-server-core@npm:^2.19.0, apollo-server-core@npm:^2.23.0": version: 2.23.0 resolution: "apollo-server-core@npm:2.23.0" dependencies: @@ -3016,6 +3137,37 @@ __metadata: languageName: node linkType: hard +"apollo-server-core@npm:^3.1.1": + version: 3.1.1 + resolution: "apollo-server-core@npm:3.1.1" + dependencies: + "@apollographql/apollo-tools": ^0.5.1 + "@apollographql/graphql-playground-html": 1.6.29 + "@graphql-tools/mock": ^8.1.2 + "@graphql-tools/schema": ^7.1.5 + "@graphql-tools/utils": ^7.9.0 + "@josephg/resolvable": ^1.0.0 + apollo-datasource: ^3.0.3 + apollo-graphql: ^0.9.0 + apollo-reporting-protobuf: ^3.0.0 + apollo-server-caching: ^3.0.1 + apollo-server-env: ^4.0.3 + apollo-server-errors: ^3.0.1 + apollo-server-plugin-base: ^3.1.1 + apollo-server-types: ^3.1.1 + async-retry: ^1.2.1 + fast-json-stable-stringify: ^2.1.0 + graphql-tag: ^2.11.0 + loglevel: ^1.6.8 + lru-cache: ^6.0.0 + sha.js: ^2.4.11 + uuid: ^8.0.0 + peerDependencies: + graphql: ^15.3.0 + checksum: 157faaa2a55a8cecf54830e447e7f10956dae99205c054a10a52ad56fca27012a63d97888480fe242368b52bad7475ce990edc73146ded01f28d273c950a88b6 + languageName: node + linkType: hard + "apollo-server-env@npm:^3.0.0": version: 3.0.0 resolution: "apollo-server-env@npm:3.0.0" @@ -3026,6 +3178,15 @@ __metadata: languageName: node linkType: hard +"apollo-server-env@npm:^4.0.3": + version: 4.0.3 + resolution: "apollo-server-env@npm:4.0.3" + dependencies: + node-fetch: ^2.6.1 + checksum: d4e0f626c54fe2850c9c3c5ce2723ff27dcd634aec51bf38c466271f42222dda02af5d26d8f39f2e6c55e4b71c46f50ae851bc5f8dee8e042e7671fa341fc1c2 + languageName: node + linkType: hard + "apollo-server-errors@npm:^2.5.0": version: 2.5.0 resolution: "apollo-server-errors@npm:2.5.0" @@ -3035,6 +3196,15 @@ __metadata: languageName: node linkType: hard +"apollo-server-errors@npm:^3.0.1": + version: 3.0.1 + resolution: "apollo-server-errors@npm:3.0.1" + peerDependencies: + graphql: ^15.3.0 + checksum: 7ffce7299ecaf3ad1f23ea5fbb580ec21fd3412730f6387270fd14a86755d58e909a107c07d334f219d894bce9ebd5d3354e99d827f4ab2d71d2db1a1d3b1e99 + languageName: node + linkType: hard + "apollo-server-express@npm:2.19.0": version: 2.19.0 resolution: "apollo-server-express@npm:2.19.0" @@ -3062,7 +3232,7 @@ __metadata: languageName: node linkType: hard -"apollo-server-express@npm:^2.21.0, apollo-server-express@npm:^2.23.0": +"apollo-server-express@npm:^2.23.0": version: 2.23.0 resolution: "apollo-server-express@npm:2.23.0" dependencies: @@ -3089,6 +3259,28 @@ __metadata: languageName: node linkType: hard +"apollo-server-express@npm:^3.1.1": + version: 3.1.1 + resolution: "apollo-server-express@npm:3.1.1" + dependencies: + "@types/accepts": ^1.3.5 + "@types/body-parser": 1.19.1 + "@types/cors": 2.8.12 + "@types/express": 4.17.13 + "@types/express-serve-static-core": 4.17.24 + accepts: ^1.3.5 + apollo-server-core: ^3.1.1 + apollo-server-types: ^3.1.1 + body-parser: ^1.19.0 + cors: ^2.8.5 + parseurl: ^1.3.3 + peerDependencies: + express: ^4.17.1 + graphql: ^15.3.0 + checksum: 25c627cfa88208b57a90153037e5489ac38b29df9f08fd0eec6b48943b7a121c22aa1b43c97bad7f3f2d3ad39adde63915834f040c6f637585226a153b30e5c3 + languageName: node + linkType: hard + "apollo-server-plugin-base@npm:^0.11.0": version: 0.11.0 resolution: "apollo-server-plugin-base@npm:0.11.0" @@ -3100,6 +3292,17 @@ __metadata: languageName: node linkType: hard +"apollo-server-plugin-base@npm:^3.1.1": + version: 3.1.1 + resolution: "apollo-server-plugin-base@npm:3.1.1" + dependencies: + apollo-server-types: ^3.1.1 + peerDependencies: + graphql: ^15.3.0 + checksum: 095cf37cc403aa8c413de79988f259d0b24eea0ef35a3b313873c8c632460e4cc3bd3aeed8d3058ff8be280887eb195c4af8915927b49c701c0b5e42ca3a7a15 + languageName: node + linkType: hard + "apollo-server-testing@npm:2.19.0": version: 2.19.0 resolution: "apollo-server-testing@npm:2.19.0" @@ -3137,19 +3340,16 @@ __metadata: languageName: node linkType: hard -"apollo-server@npm:2.21.0": - version: 2.21.0 - resolution: "apollo-server@npm:2.21.0" +"apollo-server-types@npm:^3.1.1": + version: 3.1.1 + resolution: "apollo-server-types@npm:3.1.1" dependencies: - apollo-server-core: ^2.21.0 - apollo-server-express: ^2.21.0 - express: ^4.0.0 - graphql-subscriptions: ^1.0.0 - graphql-tools: ^4.0.8 - stoppable: ^1.1.0 + apollo-reporting-protobuf: ^3.0.0 + apollo-server-caching: ^3.0.1 + apollo-server-env: ^4.0.3 peerDependencies: - graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 - checksum: ab5e80064c95a33ca9875256639197123542d1569a645537324403508a1e42d79f9bc464d1117d9f8bfc7e25e9045f417b198a0877172e71d56e62964535f417 + graphql: ^15.3.0 + checksum: 4257799fc3ef5b147bafe2e977ae0a352c9ddc37edbb2b1c669ac739ecf61ae82207753d5a73cc6b81ba04c364adfc2412a2ad5b5e27ad9ce64dc9111c206ef3 languageName: node linkType: hard @@ -3169,6 +3369,19 @@ __metadata: languageName: node linkType: hard +"apollo-server@npm:^3.0.2": + version: 3.1.1 + resolution: "apollo-server@npm:3.1.1" + dependencies: + apollo-server-core: ^3.1.1 + apollo-server-express: ^3.1.1 + express: ^4.17.1 + peerDependencies: + graphql: ^15.3.0 + checksum: 1d5a546a535087fc3cb5d3df8233212adf7077306fb90c14215b70422ba02512edf7358a73b3a2098f5786c80cbe445c9f767553c54eba81aeb6141a2db17e5f + languageName: node + linkType: hard + "apollo-tracing@npm:^0.13.0": version: 0.13.0 resolution: "apollo-tracing@npm:0.13.0" @@ -3651,7 +3864,7 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:1.19.0, body-parser@npm:^1.18.3": +"body-parser@npm:1.19.0, body-parser@npm:^1.18.3, body-parser@npm:^1.19.0": version: 1.19.0 resolution: "body-parser@npm:1.19.0" dependencies: @@ -4464,6 +4677,13 @@ __metadata: languageName: node linkType: hard +"core-js-pure@npm:^3.10.2": + version: 3.15.2 + resolution: "core-js-pure@npm:3.15.2" + checksum: 098de70a85d245422046c8859d699f8d1a5e971d12074f26e108a3db539430e641af4bb311888efbb944cfdc747d4b56ec8a1970458a2ce444cc834cabdf1772 + languageName: node + linkType: hard + "core-js@npm:^3.0.1": version: 3.11.0 resolution: "core-js@npm:3.11.0" @@ -6067,7 +6287,7 @@ __metadata: languageName: node linkType: hard -"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0": +"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": version: 2.1.0 resolution: "fast-json-stable-stringify@npm:2.1.0" checksum: 7df3fabfe445d65953b2d9d9d3958bd895438b215a40fb87dae8b2165c5169a897785eb5d51e6cf0eb03523af756e3d82ea01083f6ac6341fe16db532fee3016 @@ -10531,7 +10751,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"parseurl@npm:^1.3.2, parseurl@npm:~1.3.2, parseurl@npm:~1.3.3": +"parseurl@npm:^1.3.2, parseurl@npm:^1.3.3, parseurl@npm:~1.3.2, parseurl@npm:~1.3.3": version: 1.3.3 resolution: "parseurl@npm:1.3.3" checksum: 52c9e86cb58e38b28f1a50a6354d16648974ab7a2b91b209f97102840471de8adf524427774af6d5bc482fb7c0a6af6ba08ab37de9a1a7ae389ebe074015914b @@ -13306,6 +13526,13 @@ resolve@^2.0.0-next.3: languageName: node linkType: hard +"tslib@npm:~2.3.0": + version: 2.3.0 + resolution: "tslib@npm:2.3.0" + checksum: 7b4fc9feff0f704743c3760f5d8d708f6417fac6458159e63df3a6b1100f0736e3b99edb9fe370f274ad15160a1f49ff05cb49402534c818ff552c48e38c3e6e + languageName: node + linkType: hard + "tsutils@npm:^3.17.1": version: 3.21.0 resolution: "tsutils@npm:3.21.0"