From 5d27f14fa4f2dc46c392612d70e38e4739d101c9 Mon Sep 17 00:00:00 2001 From: Darrell Warde <8117355+darrellwarde@users.noreply.github.com> Date: Wed, 31 Jan 2024 11:18:56 +0000 Subject: [PATCH 01/23] Publish preview for all work --- .github/workflows/docs-pr.yml | 3 --- .github/workflows/docs-teardown.yml | 3 --- 2 files changed, 6 deletions(-) diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yml index 02bdd80d..db2d036b 100644 --- a/.github/workflows/docs-pr.yml +++ b/.github/workflows/docs-pr.yml @@ -6,9 +6,6 @@ name: "Verify PR" on: pull_request: - branches: - - "main" - - "dev" jobs: diff --git a/.github/workflows/docs-teardown.yml b/.github/workflows/docs-teardown.yml index a4cdd4bf..ddaea218 100644 --- a/.github/workflows/docs-teardown.yml +++ b/.github/workflows/docs-teardown.yml @@ -3,9 +3,6 @@ name: "Documentation Teardown" on: pull_request_target: - branches: - - "main" - - "dev" types: - closed From 61635074f828711c6a322d6cc426bdc6e9de9459 Mon Sep 17 00:00:00 2001 From: Lidia Zuin <102308961+lidiazuin@users.noreply.github.com> Date: Fri, 23 Feb 2024 13:36:06 +0100 Subject: [PATCH 02/23] publish 5.x docs (#113) --- antora.yml | 2 +- preview.yml | 2 +- publish.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/antora.yml b/antora.yml index 90a25d13..95617205 100644 --- a/antora.yml +++ b/antora.yml @@ -1,6 +1,6 @@ name: graphql title: Neo4j GraphQL Library -version: '4' +version: '5' start_page: ROOT:index.adoc nav: - modules/ROOT/content-nav.adoc diff --git a/preview.yml b/preview.yml index 0ace990d..e2e43508 100644 --- a/preview.yml +++ b/preview.yml @@ -24,7 +24,7 @@ urls: antora: extensions: - require: "@neo4j-antora/antora-modify-sitemaps" - sitemap_version: '4' + sitemap_version: '5' sitemap_loc_version: 'current' move_sitemaps_to_components: true - require: "@neo4j-antora/antora-page-list" diff --git a/publish.yml b/publish.yml index 1cffde5d..c2f154a7 100644 --- a/publish.yml +++ b/publish.yml @@ -7,7 +7,7 @@ site: content: sources: - url: https://github.com/neo4j/docs-graphql.git - branches: ['4.x', '3.x'] + branches: ['5.x', '4.x', '3.x'] exclude: - '!**/_includes/*' - '!**/readme.adoc' From bbd73324d17e08c333712d28e295fcf7fe14761a Mon Sep 17 00:00:00 2001 From: Darrell Warde <8117355+darrellwarde@users.noreply.github.com> Date: Wed, 28 Feb 2024 11:36:37 +0000 Subject: [PATCH 03/23] Collection of simple fixes (#107) * Add `@jwtClaim` to the directives index * Tweaks to the Getting Started guide * Remove dead links from the OGM reference --- .../authentication-and-authorization/configuration.adoc | 1 + modules/ROOT/pages/getting-started/index.adoc | 7 +++---- modules/ROOT/pages/ogm/reference.adoc | 4 ++-- modules/ROOT/pages/type-definitions/directives/index.adoc | 3 +++ 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/ROOT/pages/authentication-and-authorization/configuration.adoc b/modules/ROOT/pages/authentication-and-authorization/configuration.adoc index ac285fe2..c392d997 100644 --- a/modules/ROOT/pages/authentication-and-authorization/configuration.adoc +++ b/modules/ROOT/pages/authentication-and-authorization/configuration.adoc @@ -62,6 +62,7 @@ new Neo4jGraphQL({ }); ---- +[[authentication-and-authorization-jwt]] == JWT By default, filtering is available on https://www.rfc-editor.org/rfc/rfc7519#section-4.1[the registered claim names] in the JWT specification. diff --git a/modules/ROOT/pages/getting-started/index.adoc b/modules/ROOT/pages/getting-started/index.adoc index 9af02117..b9e1c89e 100644 --- a/modules/ROOT/pages/getting-started/index.adoc +++ b/modules/ROOT/pages/getting-started/index.adoc @@ -108,12 +108,12 @@ image::neo4j-aura-dashboard.png[width=500] === Using a Neo4j database -For a database located at the default "bolt://localhost:7687" (see more about https://neo4j.com/docs/operations-manual/current/configuration/ports[port configuration]), with the username "neo4j" and the password "password", add the following to the bottom of your `index.js` file: +For a database located at the default "neo4j://localhost:7687" (see more about https://neo4j.com/docs/operations-manual/current/configuration/ports[port configuration]), with the username "neo4j" and the password "password", add the following to the bottom of your `index.js` file: [source, javascript, indent=0] ---- const driver = neo4j.driver( - "bolt://localhost:7687", + "neo4j://localhost:7687", neo4j.auth.basic("username", "password") ); @@ -131,7 +131,6 @@ const server = new ApolloServer({ }); const { url } = await startStandaloneServer(server, { - context: async ({ req }) => ({ req }), listen: { port: 4000 }, }); @@ -179,7 +178,7 @@ const typeDefs = `#graphql `; const driver = neo4j.driver( - "bolt://localhost:7687", + "neo4j://localhost:7687", neo4j.auth.basic("username", "password") ); diff --git a/modules/ROOT/pages/ogm/reference.adoc b/modules/ROOT/pages/ogm/reference.adoc index a2abb868..4c0df3ea 100644 --- a/modules/ROOT/pages/ogm/reference.adoc +++ b/modules/ROOT/pages/ogm/reference.adoc @@ -16,7 +16,7 @@ The following sections work as a reference guide to all functionalities in OGM a |`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 xref::reference/api-reference/neo4jgraphql.adoc[`Neo4jGraphQL`]. +Takes an `input` object as a parameter, which is then passed to the `Neo4jGraphQL` constructor. a| [source, javascript, indent=0] ---- @@ -27,7 +27,7 @@ const ogm = new OGM({ |`init` |Asynchronous method to initialize the OGM. -Internally, calls xref::reference/api-reference/neo4jgraphql.adoc#api-reference-getschema[`Neo4jGraphQL.getSchema()`] to generate a GraphQL schema, and stores the result. +Internally, calls `Neo4jGraphQL.getSchema()` to generate a GraphQL schema, and stores the result. Initializes any models which have been created before this execution, and will throw an error if any of them are invalid. a| [source, javascript, indent=0] diff --git a/modules/ROOT/pages/type-definitions/directives/index.adoc b/modules/ROOT/pages/type-definitions/directives/index.adoc index 430ad795..d91fa943 100644 --- a/modules/ROOT/pages/type-definitions/directives/index.adoc +++ b/modules/ROOT/pages/type-definitions/directives/index.adoc @@ -39,6 +39,9 @@ of any required fields that is passed as arguments to the custom resolver. | xref::/type-definitions/directives/autogeneration.adoc#type-definitions-autogeneration-id[`@id`] | Marks a field as the unique ID for an object type, and allows for autogeneration of IDs. +| xref::/authentication-and-authorization/configuration.adoc#authentication-and-authorization-jwt[`@jwtClaim`] +| Marks a field as the unique ID for an object type, and allows for autogeneration of IDs. + | xref::/type-definitions/directives/default-values.adoc#type-definitions-default-values-limit[`@limit`] | Used on nodes to inject values into Cypher `LIMIT` clauses. From 3d1ccb12595f44de16223efd2e9d49ef1d41d6dc Mon Sep 17 00:00:00 2001 From: Darrell Warde <8117355+darrellwarde@users.noreply.github.com> Date: Wed, 28 Feb 2024 11:36:56 +0000 Subject: [PATCH 04/23] How-to guide on subscriptions and OGM (#101) * Add changes from https://github.com/neo4j/graphql/pull/3365 * Apply suggestions from code review Co-authored-by: Lidia Zuin <102308961+lidiazuin@users.noreply.github.com> --------- Co-authored-by: Lidia Zuin <102308961+lidiazuin@users.noreply.github.com> --- modules/ROOT/content-nav.adoc | 1 + modules/ROOT/pages/ogm/subscriptions.adoc | 247 ++++++++++++++++++ .../pages/subscriptions/getting-started.adoc | 1 + 3 files changed, 249 insertions(+) create mode 100644 modules/ROOT/pages/ogm/subscriptions.adoc diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index f16f49fa..6559b28e 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -66,6 +66,7 @@ *** xref:ogm/directives.adoc[] *** xref:ogm/selection-set.adoc[] *** xref:ogm/type-generation.adoc[] +*** xref:ogm/subscriptions.adoc[] *** xref:ogm/reference.adoc[] ** xref:driver-configuration.adoc[] diff --git a/modules/ROOT/pages/ogm/subscriptions.adoc b/modules/ROOT/pages/ogm/subscriptions.adoc new file mode 100644 index 00000000..bca80407 --- /dev/null +++ b/modules/ROOT/pages/ogm/subscriptions.adoc @@ -0,0 +1,247 @@ +[[ogm-subscriptions]] +:description: This how-to guide shows how to use the OGM with subscriptions. += How to use the OGM with subscriptions + +The Neo4j GraphQL Library can be used in conjunction with the OGM in order to extend the library's functionalities, or to take advantage of the xref:ogm/private.adoc[`@private` directive]. + +== Usage + +This section shows how to use subscriptions with the OGM inside custom resolvers. + +=== Prerequisites + +. Use the following type definitions: +[source, javascript, indent=0] +---- +const typeDefs = `#graphql + type User { + email: String! + password: String! @private + } +`; +---- + +. Set up a subscriptions plugin instance to be shared between the Library and the OGM constructors: +[source, javascript, indent=0] +---- +const subscriptionsPlugin = new Neo4jGraphQLSubscriptionsSingleInstancePlugin(); +---- + +. Set up a server that supports subscriptions. +See more instructions in the xref:subscriptions/getting-started.adoc#setting-up-server[Getting started page]. + + +=== Adding the OGM + +In order to utilize the `@private` marked field on the `User` type, you need to create the `User` model. +Also, to use the subscriptions, you need to pass in the subscriptions plugin instance to the OGM constructor: + +[source, javascript, indent=0] +---- +const ogm = new OGM({ typeDefs, driver, plugins: { subscriptions: subscriptionsPlugin } }); +const Profile = ogm.model("Profile"); +---- + +Make sure you initialize the OGM instance before using it by adding the following line to the `main()` function: +[source, javascript, indent=0] +---- +await ogm.init(); +---- + +=== Adding a custom resolver + +xref:custom-resolvers/[Custom resolvers] can be used for multiple reasons such as performing data manipulation and checks, or interact with third party systems. +In this case, you only need to create a `User` node with the `password` field set. +You can do that by adding a sign-up mutation. +For example: + +[source, javascript, indent=0] +---- +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 { users } = await User.create({ + input: [ + { + username, + password, + } + ] + }); + return createJWT({ sub: users[0].id }); + }, + }, +}; +const typeDefs = ` + type Mutation { + signUp(username: String!, password: String!): String! + } +` +---- + +[discrete] +=== Conclusion + +Altogether, it should look like this: + +[source, javascript, indent=0] +---- +const subscriptionsPlugin = new Neo4jGraphQLSubscriptionsSingleInstancePlugin(); +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); +const typeDefs = ` + type User { + email: String! + password: String! @private + } + type Mutation { + signUp(username: String!, password: String!): String! # custom resolver + } +` +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 { users } = await User.create({ + input: [ + { + username, + password, + } + ] + }); + return createJWT({ sub: users[0].id }); + }, + }, +}; +// Share the subscriptions plugin instance across the Library and the OGM +const neoSchema = new Neo4jGraphQL({ + typeDefs, + driver, + resolvers, + plugins: { + subscriptions: subscriptionsPlugin, + }, +}); +const ogm = new OGM({ typeDefs, driver, plugins: { subscriptions: subscriptionsPlugin } }); +const Profile = ogm.model("Profile"); + +async function main() { + // initialize the OGM instance + await ogm.init(); + + // Apollo server setup with WebSockets + const app = express(); + const httpServer = createServer(app); + const wsServer = new WebSocketServer({ + server: httpServer, + path: "/graphql", + }); + + // Neo4j schema + const schema = await neoSchema.getSchema(); + + const serverCleanup = useServer( + { + schema, + context: (ctx) => { + return ctx; + }, + }, + wsServer + ); + + const server = new ApolloServer({ + schema, + plugins: [ + ApolloServerPluginDrainHttpServer({ + httpServer, + }), + { + async serverWillStart() { + return Promise.resolve({ + async drainServer() { + await serverCleanup.dispose(); + }, + }); + }, + }, + ], + }); + await server.start(); + + app.use( + "/graphql", + cors(), + bodyParser.json(), + expressMiddleware(server, { + context: async ({ req }) => ({ req }), + }) + ); + + const PORT = 4000; + httpServer.listen(PORT, () => { + console.log(`Server is now running on http://localhost:${PORT}/graphql`); + }); +} +---- + + +== Receiving the subscription events + +First, run the following subscription to receive `User` creation events: +[source, gql, indent=0] +---- +subscription { + userCreated { + createdUser { + email + } + event + } +} +---- + +Then run the sign-up mutation: +[source, gql, indent=0] +---- +mutation { + signUp(email: "jon.doe@xyz.com", password: "jondoe") { + email + password + } +} +---- + +The results should look like this: +[source, gql, indent=0] +---- +{ + "data": { + "userCreated": { + "createdUser": { + "email": "jon.doe@xyz.com", + "password": "jondoe" + }, + "event": "CREATE" + } + } +} +---- diff --git a/modules/ROOT/pages/subscriptions/getting-started.adoc b/modules/ROOT/pages/subscriptions/getting-started.adoc index a31578ac..137eaf6e 100644 --- a/modules/ROOT/pages/subscriptions/getting-started.adoc +++ b/modules/ROOT/pages/subscriptions/getting-started.adoc @@ -33,6 +33,7 @@ Then, the next step is to install the following dependencies: npm i --save ws graphql-ws neo4j-driver @neo4j/graphql express @apollo/server body-parser cors ---- +[setting-up-server] == Set up an `@apollo/server` server Add the following code to your `index.js` file to implement a simple `@apollo/server` server with subscriptions (for more options, see link:https://www.apollographql.com/docs/apollo-server/data/subscriptions/[Apollo's documentation]): From 33092e31e2764b637d93d8b68666444a31eceda8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 16:44:37 +0000 Subject: [PATCH 05/23] Bump the prod-dependencies group with 3 updates (#94) Bumps the prod-dependencies group with 3 updates: [@antora/cli](https://gitlab.com/antora/antora), [@antora/site-generator-default](https://gitlab.com/antora/antora) and @neo4j-antora/aliases-redirects. Updates `@antora/cli` from 3.1.6 to 3.1.7 - [Changelog](https://gitlab.com/antora/antora/blob/main/CHANGELOG.adoc) - [Commits](https://gitlab.com/antora/antora/compare/v3.1.6...v3.1.7) Updates `@antora/site-generator-default` from 3.1.6 to 3.1.7 - [Changelog](https://gitlab.com/antora/antora/blob/main/CHANGELOG.adoc) - [Commits](https://gitlab.com/antora/antora/compare/v3.1.6...v3.1.7) Updates `@neo4j-antora/aliases-redirects` from 0.2.2 to 0.2.3 --- updated-dependencies: - dependency-name: "@antora/cli" dependency-type: direct:production update-type: version-update:semver-patch dependency-group: prod-dependencies - dependency-name: "@antora/site-generator-default" dependency-type: direct:production update-type: version-update:semver-patch dependency-group: prod-dependencies - dependency-name: "@neo4j-antora/aliases-redirects" dependency-type: direct:production update-type: version-update:semver-patch dependency-group: prod-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 186 +++++++++++++++++++++++----------------------- package.json | 6 +- 2 files changed, 96 insertions(+), 96 deletions(-) diff --git a/package-lock.json b/package-lock.json index 477325ce..1b741075 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,9 @@ "version": "4.0.0", "license": "ISC", "dependencies": { - "@antora/cli": "^3.1.5", - "@antora/site-generator-default": "^3.1.5", - "@neo4j-antora/aliases-redirects": "^0.2.2", + "@antora/cli": "^3.1.7", + "@antora/site-generator-default": "^3.1.7", + "@neo4j-antora/aliases-redirects": "^0.2.3", "@neo4j-antora/antora-add-notes": "^0.3.1", "@neo4j-antora/antora-modify-sitemaps": "^0.4.4", "@neo4j-antora/antora-page-list": "^0.1.1", @@ -26,11 +26,11 @@ } }, "node_modules/@antora/asciidoc-loader": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@antora/asciidoc-loader/-/asciidoc-loader-3.1.6.tgz", - "integrity": "sha512-Tqy4QFuZiKe/yX+3H8+vTLE6HH+VDm9OkKwq3G769jcC+40wz6y3poV4r4t1XJFAWwa/AKGM99ZcnJcJ3rtW+A==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@antora/asciidoc-loader/-/asciidoc-loader-3.1.7.tgz", + "integrity": "sha512-sM/poPtAi8Cx0g2oLGHoEYjTdE9pvIYLgbHW07fGf6c9wQYMd2DMsevtbhNKWp+xqxj/QinToz4JOaNLoy1nfg==", "dependencies": { - "@antora/logger": "3.1.6", + "@antora/logger": "3.1.7", "@antora/user-require-helper": "~2.0", "@asciidoctor/core": "~2.2" }, @@ -39,12 +39,12 @@ } }, "node_modules/@antora/cli": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@antora/cli/-/cli-3.1.6.tgz", - "integrity": "sha512-aJLN6JyXYUfMfMVr/6JEenxnnG8evKfyp01wd/Cj7mkJXD/lj/Gqws2bXyP/hjUEl/HuX4/P9AcIfMLT/vfQJw==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@antora/cli/-/cli-3.1.7.tgz", + "integrity": "sha512-yHo30VmiLLsZU4JW8VZR6fql9m5lIxocA2d0tduKQ+r4YSD1kt+bbwX3You3iMwW7oLoNo62zfU76F8CQBnm2g==", "dependencies": { - "@antora/logger": "3.1.6", - "@antora/playbook-builder": "3.1.6", + "@antora/logger": "3.1.7", + "@antora/playbook-builder": "3.1.7", "@antora/user-require-helper": "~2.0", "commander": "~10.0" }, @@ -56,18 +56,18 @@ } }, "node_modules/@antora/content-aggregator": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@antora/content-aggregator/-/content-aggregator-3.1.6.tgz", - "integrity": "sha512-kOKWn/1gBvd9XOp00/wFzH4lb3yCa5u65ZKmfe9VwC7uOl5Tg9zT0lxMa7miEbPAmfhcOr0zRYXa2ybsoKBWNw==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@antora/content-aggregator/-/content-aggregator-3.1.7.tgz", + "integrity": "sha512-2gBbxaDxqY4QRw9Vut0IbKNqI9zAAohxjT0zcA5Am10kE3ywvzXaBa3tvb+A7vUl/vRcCC0LPM7pO37RVrbsGA==", "dependencies": { "@antora/expand-path-helper": "~2.0", - "@antora/logger": "3.1.6", + "@antora/logger": "3.1.7", "@antora/user-require-helper": "~2.0", "braces": "~3.0", "cache-directory": "~2.0", "glob-stream": "~7.0", "hpagent": "~1.2", - "isomorphic-git": "~1.21", + "isomorphic-git": "~1.25", "js-yaml": "~4.1", "multi-progress": "~4.0", "picomatch": "~2.3", @@ -81,12 +81,12 @@ } }, "node_modules/@antora/content-classifier": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@antora/content-classifier/-/content-classifier-3.1.6.tgz", - "integrity": "sha512-e5Fs38Cfbl2kecxpRLFftflphbjg2wPfWlwjLZjs8d0R5ISSCM38q8ecDKCQHQlrYJkSrxiSpWqg0irNqAHnLw==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@antora/content-classifier/-/content-classifier-3.1.7.tgz", + "integrity": "sha512-94XwJ35pkWJU6dJqQg5oreJRCkaXwU6yw9XSyB731o4i/0romkazKnu7deHugqdgvzqn92AlMRG6R0clhJLEsw==", "dependencies": { - "@antora/asciidoc-loader": "3.1.6", - "@antora/logger": "3.1.6", + "@antora/asciidoc-loader": "3.1.7", + "@antora/logger": "3.1.7", "mime-types": "~2.1", "vinyl": "~2.2" }, @@ -95,11 +95,11 @@ } }, "node_modules/@antora/document-converter": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@antora/document-converter/-/document-converter-3.1.6.tgz", - "integrity": "sha512-bdzlkwq1WMnfdc6FUZNcO58LwjMqYmv3m9dI/eAJryGiKa9ChBFskwA1ob7rB87Qxjzu6UHcNucbt910hjEOAw==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@antora/document-converter/-/document-converter-3.1.7.tgz", + "integrity": "sha512-cRVJf7QyclxjWbA0gWz7hncZYThIREikkwaxaa26LsRCfBNlw7wxs7lWejvIvEl1LVshupbinJwKUPPQPOsHhw==", "dependencies": { - "@antora/asciidoc-loader": "3.1.6" + "@antora/asciidoc-loader": "3.1.7" }, "engines": { "node": ">=16.0.0" @@ -114,9 +114,9 @@ } }, "node_modules/@antora/file-publisher": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@antora/file-publisher/-/file-publisher-3.1.6.tgz", - "integrity": "sha512-UPTyFWY7lrG/Qj6SBxgoVBg1fW3JemJzW62y6pKuGHF59TEKJiaVTUlHEaVgkbpkCngn2os+VOX7fHK0jsBU9A==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@antora/file-publisher/-/file-publisher-3.1.7.tgz", + "integrity": "sha512-UH2o0DJuv9BJvWgn+QSE3MhtHB3oN3lG5lJkV3fQi1jAV+qPIHoiTIYhbw02mj5KQ3Qbt7YWWAKZKuGl3rEdjg==", "dependencies": { "@antora/expand-path-helper": "~2.0", "@antora/user-require-helper": "~2.0", @@ -129,9 +129,9 @@ } }, "node_modules/@antora/logger": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@antora/logger/-/logger-3.1.6.tgz", - "integrity": "sha512-36kU8gMbPslcPu8u9EbXsz6+9G9+LWPKhO7n8mEQqxlcCqmChwgetK6VbsL102rynpgPsstX6IAKg2wbptJK5g==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@antora/logger/-/logger-3.1.7.tgz", + "integrity": "sha512-Z2tfNIi9G4BnAZq26Kp30974FxCVCtvH46FOi6ClnkJg6Uf2gTrVlJERmtsDTsHjWsf1qKbnj/4b99/AU31iQg==", "dependencies": { "@antora/expand-path-helper": "~2.0", "pino": "~8.14", @@ -143,22 +143,22 @@ } }, "node_modules/@antora/navigation-builder": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@antora/navigation-builder/-/navigation-builder-3.1.6.tgz", - "integrity": "sha512-0iqktzBKQ4kgOM+pbm1bYdGUlN6Blw5zAxURr+W7X96b45mUHCTNz1nZrsDvBLbVXpSTk//ih85Ioh0RU4RJTw==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@antora/navigation-builder/-/navigation-builder-3.1.7.tgz", + "integrity": "sha512-QvMPb0qY1zfgyLCfuEhJOpO5qSVjaVe5X/bQjSii9vDGgpIEiC2yt/hgqER37E/3zsBGEZvCH5lSLk1c7x0+EQ==", "dependencies": { - "@antora/asciidoc-loader": "3.1.6" + "@antora/asciidoc-loader": "3.1.7" }, "engines": { "node": ">=16.0.0" } }, "node_modules/@antora/page-composer": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@antora/page-composer/-/page-composer-3.1.6.tgz", - "integrity": "sha512-zl2UXVmHEy23zWsGzz3ZpnqtzTVfYvAVS7osPZpSyto3unwtQUI0dR+JpWndElsEDt71JJedbsXa/y/tNC2ZOQ==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@antora/page-composer/-/page-composer-3.1.7.tgz", + "integrity": "sha512-zJMzYznPT6Vd59nEXio/2rolkX070Nup6g4a8d4RCz0WoE8dmMidw6XFgjAHr0Lyh14/FHgBPlYXfhkDFR16Mw==", "dependencies": { - "@antora/logger": "3.1.6", + "@antora/logger": "3.1.7", "handlebars": "~4.7", "require-from-string": "~2.0" }, @@ -167,9 +167,9 @@ } }, "node_modules/@antora/playbook-builder": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@antora/playbook-builder/-/playbook-builder-3.1.6.tgz", - "integrity": "sha512-bZcDastZViAgPVPNvvbbw7ci63gL5YnyG5X7NuHJoORgzyGQAsMYEjzfa9yfNfXubUmXv/oSteUSxbACjdjzWg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@antora/playbook-builder/-/playbook-builder-3.1.7.tgz", + "integrity": "sha512-lU80S1BqUy9DvqziEzRwpYTaWhOshxgrGAjf/F5VjAIaHCGVx0rZgfoI2rgFFkbVaH94kauOngdtTXDPXC1fPQ==", "dependencies": { "@iarna/toml": "~2.2", "convict": "~6.2", @@ -181,9 +181,9 @@ } }, "node_modules/@antora/redirect-producer": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@antora/redirect-producer/-/redirect-producer-3.1.6.tgz", - "integrity": "sha512-tzlrJa2vny0HPBtIAgEM/xCMTfOi4z2CYUt4Ctz7rV8PBv6NOjlLkbu7Goc57CpR9wmJ3C4AGJcVHN0tah0FmA==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@antora/redirect-producer/-/redirect-producer-3.1.7.tgz", + "integrity": "sha512-6zAHfcOb0v0829nAbn/3HMilbactjbjU7zBT9Iy6JHQfbqXT/g/mUT9N13Lj/wbq/nm0qKQJweB0Mi6BS2fbMw==", "dependencies": { "vinyl": "~2.2" }, @@ -192,23 +192,23 @@ } }, "node_modules/@antora/site-generator": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@antora/site-generator/-/site-generator-3.1.6.tgz", - "integrity": "sha512-NKRTVDGB7CuzEBx68iQGweSTDXLvqccExEfBiudPEhyagcn4U+fwef+0LjE4A2imcpD/QQC7l5U8VaNObQYnRQ==", - "dependencies": { - "@antora/asciidoc-loader": "3.1.6", - "@antora/content-aggregator": "3.1.6", - "@antora/content-classifier": "3.1.6", - "@antora/document-converter": "3.1.6", - "@antora/file-publisher": "3.1.6", - "@antora/logger": "3.1.6", - "@antora/navigation-builder": "3.1.6", - "@antora/page-composer": "3.1.6", - "@antora/playbook-builder": "3.1.6", - "@antora/redirect-producer": "3.1.6", - "@antora/site-mapper": "3.1.6", - "@antora/site-publisher": "3.1.6", - "@antora/ui-loader": "3.1.6", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@antora/site-generator/-/site-generator-3.1.7.tgz", + "integrity": "sha512-39KWip9bLdQ+4ssyqjI+O0COquKHxoeznxY2/3w5pNZEoeTg8cD7kOsnWfbocw0R3Rj+kJV5MnqICFNq0nuPeA==", + "dependencies": { + "@antora/asciidoc-loader": "3.1.7", + "@antora/content-aggregator": "3.1.7", + "@antora/content-classifier": "3.1.7", + "@antora/document-converter": "3.1.7", + "@antora/file-publisher": "3.1.7", + "@antora/logger": "3.1.7", + "@antora/navigation-builder": "3.1.7", + "@antora/page-composer": "3.1.7", + "@antora/playbook-builder": "3.1.7", + "@antora/redirect-producer": "3.1.7", + "@antora/site-mapper": "3.1.7", + "@antora/site-publisher": "3.1.7", + "@antora/ui-loader": "3.1.7", "@antora/user-require-helper": "~2.0" }, "engines": { @@ -216,22 +216,22 @@ } }, "node_modules/@antora/site-generator-default": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@antora/site-generator-default/-/site-generator-default-3.1.6.tgz", - "integrity": "sha512-HXnqYYQdQHwjg6NAvEr3oolt4Kmb3cEVqMsR9BoN8ZPfVpnx+kM9r1/qqpgx9BZxY2oTDYPhFFeygdq2Fu5rIg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@antora/site-generator-default/-/site-generator-default-3.1.7.tgz", + "integrity": "sha512-m9UbejttKzp8MKJTEc+aKXi5SNb864QO7lQiQzSR0fiWnIR8WIM73CPPwkVeOXdKqaJvQp5IF9rlXXTkkC19fw==", "dependencies": { - "@antora/site-generator": "3.1.6" + "@antora/site-generator": "3.1.7" }, "engines": { "node": ">=16.0.0" } }, "node_modules/@antora/site-mapper": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@antora/site-mapper/-/site-mapper-3.1.6.tgz", - "integrity": "sha512-Jc5AqY2uS3wO21iwEFyJuXOspTLN6UdNnZP/Os2oguR+cSsjwUx+l6+X7lquIndq+dXUQS3tMQkwNkhLgfcsrw==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@antora/site-mapper/-/site-mapper-3.1.7.tgz", + "integrity": "sha512-x++89btbwk8FxyU2+H/RHQMnsC9sdvQvXcwXwNt11eXN1qj7t8TPiQZTalg7gkf0/osY4l7JRpGBY5JJfOJVig==", "dependencies": { - "@antora/content-classifier": "3.1.6", + "@antora/content-classifier": "3.1.7", "vinyl": "~2.2" }, "engines": { @@ -239,20 +239,20 @@ } }, "node_modules/@antora/site-publisher": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@antora/site-publisher/-/site-publisher-3.1.6.tgz", - "integrity": "sha512-AOpM12gmMJeucebEGneHvOJAXQgco0lAg7Vx9CH7slHVeJy6mM74Mcut7KkKlv3AOJJKgYfdYkJndvq9dqbWmQ==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@antora/site-publisher/-/site-publisher-3.1.7.tgz", + "integrity": "sha512-zHaJc7UeBfFSIhBbQ5U5Ud4u671M84oqSJb3pPxlUiJDP7iVJlSl+0GNm0NAIoDizjPtVksUI88fzqCy5rfSUQ==", "dependencies": { - "@antora/file-publisher": "3.1.6" + "@antora/file-publisher": "3.1.7" }, "engines": { "node": ">=16.0.0" } }, "node_modules/@antora/ui-loader": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@antora/ui-loader/-/ui-loader-3.1.6.tgz", - "integrity": "sha512-IivfKW7fCaV7GpXIOxyk8X2mJiYoM6U0CDaFzWiKerJeDikW1Oi9KGxCMe2+onYBJrgzQxAZsIzjr9fXUcDZWw==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@antora/ui-loader/-/ui-loader-3.1.7.tgz", + "integrity": "sha512-79QuZB0c91dveoESa3RcE18ZZFJo0rDZX9aJKHVc20dInQBGCgfURPqB2OytkzaXD3lNtDJ41yjKNYZqsAQy1Q==", "dependencies": { "@antora/expand-path-helper": "~2.0", "@vscode/gulp-vinyl-zip": "~2.5", @@ -301,9 +301,9 @@ "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==" }, "node_modules/@neo4j-antora/aliases-redirects": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@neo4j-antora/aliases-redirects/-/aliases-redirects-0.2.2.tgz", - "integrity": "sha512-/y0/dA0lDMRqnBeBu61LLC/sbN249efsXsM5g5Iw0Ln98bfazgU/MdfBK3ZshpADM4LQLwswDVA91p0nN40Dvw==" + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@neo4j-antora/aliases-redirects/-/aliases-redirects-0.2.3.tgz", + "integrity": "sha512-r5XB4FHnMFYg2aPjpzdmyBwjKLiCcgIlmF5V6CzzgJKC06qxXPZmmQzVDsx3MS2PVRXs0rPRBjRFUqC++4Z8Xg==" }, "node_modules/@neo4j-antora/antora-add-notes": { "version": "0.3.1", @@ -435,9 +435,9 @@ } }, "node_modules/async-lock": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.0.tgz", - "integrity": "sha512-coglx5yIWuetakm3/1dsX9hxCNox22h7+V80RQOu2XUUMidtArxKoZoOtHUPuR84SycKTXzgGzAUR5hJxujyJQ==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", + "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==" }, "node_modules/atomic-sleep": { "version": "1.0.0", @@ -1403,9 +1403,9 @@ ] }, "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "engines": { "node": ">= 4" } @@ -1552,9 +1552,9 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "node_modules/isomorphic-git": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.21.0.tgz", - "integrity": "sha512-ZqCAUM63CYepA3fB8H7NVyPSiOkgzIbQ7T+QPrm9xtYgQypN9JUJ5uLMjB5iTfomdJf3mdm6aSxjZwnT6ubvEA==", + "version": "1.25.6", + "resolved": "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.25.6.tgz", + "integrity": "sha512-zA3k3QOO7doqOnBgwsaXJwHKSIIl5saEdH4xxalu082WHVES4KghsG6RE2SDwjXMCIlNa1bWocbitH6bRIrmLQ==", "dependencies": { "async-lock": "^1.1.0", "clean-git-ref": "^2.0.1", @@ -2565,9 +2565,9 @@ } }, "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==" }, "node_modules/string_decoder": { "version": "1.3.0", diff --git a/package.json b/package.json index 43acf3ed..e7316d04 100644 --- a/package.json +++ b/package.json @@ -19,13 +19,13 @@ "author": "Neo4j", "license": "ISC", "dependencies": { - "@antora/cli": "^3.1.5", - "@antora/site-generator-default": "^3.1.5", + "@antora/cli": "^3.1.7", + "@antora/site-generator-default": "^3.1.7", "@neo4j-antora/antora-add-notes": "^0.3.1", "@neo4j-antora/antora-modify-sitemaps": "^0.4.4", "@neo4j-antora/antora-page-list": "^0.1.1", "@neo4j-antora/antora-page-roles": "^0.3.2", - "@neo4j-antora/aliases-redirects": "^0.2.2", + "@neo4j-antora/aliases-redirects": "^0.2.3", "@neo4j-antora/antora-table-footnotes": "^0.3.2", "@neo4j-documentation/macros": "^1.0.2", "@neo4j-documentation/remote-include": "^1.0.0" From ee5878396677c75caf05c42fd7bd38bbd1346553 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 16:45:40 +0000 Subject: [PATCH 06/23] Bump the dev-dependencies group with 1 update (#114) Bumps the dev-dependencies group with 1 update: [nodemon](https://github.com/remy/nodemon). Updates `nodemon` from 3.0.2 to 3.1.0 - [Release notes](https://github.com/remy/nodemon/releases) - [Commits](https://github.com/remy/nodemon/compare/v3.0.2...v3.1.0) --- updated-dependencies: - dependency-name: nodemon dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1b741075..6c2c328f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ }, "devDependencies": { "express": "^4.18.2", - "nodemon": "^3.0.2" + "nodemon": "^3.1.0" } }, "node_modules/@antora/asciidoc-loader": { @@ -1798,9 +1798,9 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "node_modules/nodemon": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.2.tgz", - "integrity": "sha512-9qIN2LNTrEzpOPBaWHTm4Asy1LxXLSickZStAQ4IZe7zsoIpD/A7LWxhZV3t4Zu352uBcqVnRsDXSMR2Sc3lTA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz", + "integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==", "dev": true, "dependencies": { "chokidar": "^3.5.2", diff --git a/package.json b/package.json index e7316d04..8f6c9dae 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ }, "devDependencies": { "express": "^4.18.2", - "nodemon": "^3.0.2" + "nodemon": "^3.1.0" }, "overrides": { "@antora/site-generator-default": { From 99bb4b30a2c45373b21dbb04b50c1486cd625ecc Mon Sep 17 00:00:00 2001 From: Neil Dewhurst Date: Fri, 1 Mar 2024 16:46:01 +0000 Subject: [PATCH 07/23] Update dependabot.yml --- .github/dependabot.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1e63f2a9..198fda0e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -14,3 +14,6 @@ updates: dependency-type: "production" dev-dependencies: dependency-type: "development" + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-minor", "version-update:semver-patch"] From 67cf80d84a3ed602dfe1dda6892365ce7624e7d7 Mon Sep 17 00:00:00 2001 From: Neil Dewhurst Date: Fri, 1 Mar 2024 16:48:42 +0000 Subject: [PATCH 08/23] Create CODEOWNERS --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..4aa6ecc0 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +./.github/ @recrwplay From 585d7dc8a84d0898556dfbd2231aed10723f9814 Mon Sep 17 00:00:00 2001 From: angrykoala Date: Mon, 29 Jan 2024 10:23:31 +0100 Subject: [PATCH 09/23] Move migration to 4.0 folder --- modules/ROOT/content-nav.adoc | 5 +- .../migration/{ => 4.0.0}/authorization.adoc | 0 modules/ROOT/pages/migration/4.0.0/index.adoc | 1012 +++++++++ .../ROOT/pages/migration/{ => 4.0.0}/ogm.adoc | 0 modules/ROOT/pages/migration/index.adoc | 6 +- yarn.lock | 1958 +++++++++++++++++ 6 files changed, 2976 insertions(+), 5 deletions(-) rename modules/ROOT/pages/migration/{ => 4.0.0}/authorization.adoc (100%) create mode 100644 modules/ROOT/pages/migration/4.0.0/index.adoc rename modules/ROOT/pages/migration/{ => 4.0.0}/ogm.adoc (100%) create mode 100644 yarn.lock diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index 6559b28e..e1df5753 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -58,8 +58,9 @@ ** xref:introspector.adoc[Introspector] ** xref:migration/index.adoc[Migration guide] -*** xref:migration/authorization.adoc[] -*** xref:migration/ogm.adoc[] +*** xref:migration/4.0.0/index.adoc[4.0.0 Guide] +**** xref:migration/4.0.0/authorization.adoc[] +**** xref:migration/4.0.0/ogm.adoc[] ** xref:ogm/index.adoc[] *** xref:ogm/installation.adoc[] diff --git a/modules/ROOT/pages/migration/authorization.adoc b/modules/ROOT/pages/migration/4.0.0/authorization.adoc similarity index 100% rename from modules/ROOT/pages/migration/authorization.adoc rename to modules/ROOT/pages/migration/4.0.0/authorization.adoc diff --git a/modules/ROOT/pages/migration/4.0.0/index.adoc b/modules/ROOT/pages/migration/4.0.0/index.adoc new file mode 100644 index 00000000..72b159bb --- /dev/null +++ b/modules/ROOT/pages/migration/4.0.0/index.adoc @@ -0,0 +1,1012 @@ +[[v4-migration]] +:description: This page lists the breaking changes from version 3.0.0 to 4.0.0 and describes how to update. +:page-aliases: guides/v4-migration/index.adoc , migration/v4-migration/index.adoc += Migration to 4.0.0 + +This page lists all breaking changes from the Neo4j GraphQL Library version 3.x to 4.x and how to update it. + +== How to update + +To update your Neo4j GraphQL Library, use npm or the package manager of choice: + +[source, bash, indent=0] +---- +npm update @neo4j/graphql +---- + +== Breaking changes + +Here is a list of all the breaking changes from version 3.0.0 to 4.0.0. + +=== `IExecutableSchemaDefinition` + +If you were passing any arguments from https://the-guild.dev/graphql/tools/docs/api/interfaces/schema_src.iexecutableschemadefinition[`IExecutableSchemaDefinition`] into the library other than `typeDefs` and `resolvers`, these are no longer supported. + +=== `config.enableDebug` + +The programmatic toggle for debug logging has been moved from `config.enableDebug` to simply `debug`. + +[cols="1,1"] +|=== +|Before | Now + +a| +[source, javascript, indent=0] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const neo4j = require("neo4j-driver"); +const { ApolloServer } = require("apollo-server"); + +const typeDefs = ` + type Movie { + title: String! + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const neoSchema = new Neo4jGraphQL({ + typeDefs, + driver, + config: { + enableDebug: true, + } +}); +---- +a| +[source, javascript, indent=0] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const neo4j = require("neo4j-driver"); +const { ApolloServer } = require("apollo-server"); + +const typeDefs = ` + type Movie { + title: String! + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const neoSchema = new Neo4jGraphQL({ + typeDefs, + driver, + debug: true, +}); +---- +|=== + +=== `driverConfig` + +Session configuration is now available only in the context under the `sessionConfig` key. +Additionally, the `bookmarks` key has been removed as it is no longer needed with the bookmark manager of the newer driver. + +[cols="1,1"] +|=== +|Before | Now + +a| +[source, javascript, indent=0] +---- +import { ApolloServer } from '@apollo/server'; +import { startStandaloneServer } from '@apollo/server/standalone'; +import { Neo4jGraphQL } from "@neo4j/graphql"; +import neo4j from "neo4j-driver"; + +const typeDefs = `#graphql + type User { + name: String + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const neoSchema = new Neo4jGraphQL({ + typeDefs, + config: { + driverConfig: { + database: "different-db" + }, + }, +}) + +const server = new ApolloServer({ + schema: await neoSchema.getSchema(), +}); + +await startStandaloneServer(server, { + context: async ({ req }) => ({ req }), +}); + +---- +a| +[source, javascript, indent=0] +---- +import { ApolloServer } from '@apollo/server'; +import { startStandaloneServer } from '@apollo/server/standalone'; +import { Neo4jGraphQL } from "@neo4j/graphql"; +import neo4j from "neo4j-driver"; + +const typeDefs = `#graphql + type User { + name: String + } +`; + +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("neo4j", "password") +); + +const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); + +const server = new ApolloServer({ + schema: await neoSchema.getSchema(), +}); + +await startStandaloneServer(server, { + context: async ({ req }) => ({ sessionConfig: { database: "my-database" }}), +}); +---- +|=== + +=== `config.enableRegex` + +This has been replaced by `MATCHES` in features.filters. +With this change comes more granularity in the feature configuration. +You can now enable the `MATCHES` filter on `String` and `ID` fields separately: + +[cols="1,1"] +|=== +|Before | After + +a| +[source, javascript, indent=0] +---- +neoSchema = new Neo4jGraphQL({ + typeDefs, + config: { + enableRegex: true + } +}); +---- +a| +[source, javascript, indent=0] +---- +neoSchema = new Neo4jGraphQL({ + typeDefs, + features: { + filters: { + String: { + MATCHES: true, + }, + ID: { + MATCHES: true, + }, + }, + }, +}); +---- +|=== + +=== `queryOptions` + +If you had a need to pass in Cypher query options for query tuning, this interface has been changed. +The config option `queryOptions` has now become `cypherQueryOptions` inside the context function, and it now accepts simple strings instead of enums. +This change reflects the fact that the Cypher query options are set on a per-request basis. + +[cols="1,1"] +|=== +|Before | Now + +a| +[source, javascript, indent=0] +---- +const { Neo4jGraphQL, CypherRuntime } = require("@neo4j/graphql"); +const { ApolloServer } = require("apollo-server"); + +const typeDefs = ` + type Movie { + title: String! + } +`; + +const neoSchema = new Neo4jGraphQL({ + typeDefs, + config: { + queryOptions: { + runtime: CypherRuntime.INTERPRETED, + }, + }, +}); + +const server = new ApolloServer({ + schema: await neoSchema.getSchema(), +}); + +await startStandaloneServer(server, { + context: async ({ req }) => ({ req }), +}); +---- + +a| +[source, javascript, indent=0] +---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const { ApolloServer } = require("apollo-server"); + +const typeDefs = ` + type Movie { + title: String! + } +`; + +const neoSchema = new Neo4jGraphQL({ + typeDefs, +}); + +const server = new ApolloServer({ + schema: await neoSchema.getSchema(), +}); + +await startStandaloneServer(server, { + context: async ({ req }) => ({ cypherQueryOptions: { runtime: "interpreted" }}), +}); +---- +|=== + +=== `skipValidateTypeDefs` + +The argument has been moved to the top-level of the constructor input and renamed `validate`, which defaults to `true`. +If you started using the `config.startupValidation` option, this has also been rolled into the same `validate` setting for simplicity. + +Likewise, the `resolvers` option is now just a warning, and `noDuplicateRelationshipFields` is now a mandatory check rolled into `validate`. + +Here is an example query of how it looks now: + +[cols="1,1"] +|=== +|Before | After + +a| +[source, javascript, indent=0] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, + config: { + skipValidateTypeDefs: true, + }, +}) +---- +a| +[source, javascript, indent=0] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, + validate: false, +}) +---- +|=== + +=== `@cypher` + +The default behavior of the `@cypher` directive regarding the translation has changed. +Instead of using https://neo4j.com/labs/apoc/4.0/overview/apoc.cypher/apoc.cypher.runFirstColumnMany/[apoc.cypher.runFirstColumnMany], it directly wraps the query within a `CALL { }` subquery. + +This update has proven to be more performant for the same queries, however, it may lead to unexpected changes, mainly when using Neo4j 5.x, where the subqueries need to be _aliased_. + +On top of that, to improve performance, it is recommended to pass the returned alias in the property `columnName`, to ensure the subquery is properly integrated into the larger query. + +For example, the GraphQL query: + +[source, graphql, indent=0] +---- +type query { + test: String! @cypher(statement: "RETURN 'hello'") +} +---- + +Would be translated to: +[source,cypher, indent=0] +---- +CALL { + RETURN 'hello' +} +WITH 'hello' AS this +RETURN this +---- + +Which is invalid in Neo4j 5.x. +To fix it, ensure the `RETURN` elements are aliased: + +[source, graphql, indent=0] +---- +type query { + test: String! @cypher(statement: "RETURN 'hello' as result") +} +---- + +Another way to use this update is through an experimental option with the `columnName` flag in the `@cypher` directive: + +[source, graphql, indent=0] +---- +type query { + test: String! @cypher(statement: "RETURN 'hello' as result", columnName: "result") +} +---- + +Note that escaping strings are no longer needed in Neo4j GraphQL 4.0.0. + +=== `@fulltext` + +In version 4.0.0, a number of improvements have been made to full-text queries. +These include the ability to return the full-text score, filter by the score and sorting by the score. +However, these improvements required a number of breaking changes. + +==== Full-text queries + +Full-text queries now need to be performed using a top-level query, instead of being performed using an argument on a node query. + +As a result, the following query is now invalid: + +[source, graphql, indent=0] +---- +query { + movies(fulltext: { movieTitleIndex: { phrase: "Some Title" } }) { + title + } +} +---- + +The new top-level queries can be used to return the full-text score, which indicates the confidence of a match, as well as the nodes that have been matched. +They now accept the following arguments: + +* `phrase`: specifies the string to search for in the full-text index. +* `where`: accepts a min/max score as well as the normal filters available on a node. +* `sort: used to sort using the score and node attributes. +* `limit`: used to limit the number of results to the given integer. +* `offset`: used to offset by the given number of results. + +This means that, for the following type definition: + +[source, graphql, indent=0] +---- +type Movie @fulltext(indexes: [{ indexName: "MovieTitle", fields: ["title"] }]) { # Note that indexName is the new name for the name argument. More about this below. + title: String! +} +---- + +The following top-level query and type definitions would be generated by the library: + +[source, graphql, indent=0] +---- +type Query { + movieFulltextMovieTitle(phrase: String!, where: MovieFulltextWhere, sort: [MovieFulltextSort!], limit: Int, offset: Int): [MovieFulltextResult!]! +} + +"""The result of a fulltext search on an index of Movie""" +type MovieFulltextResult { + score: Float + movies: Movie +} + +"""The input for filtering a fulltext query on an index of Movie""" +input MovieFulltextWhere { + score: FloatWhere + movie: MovieWhere +} + +"""The input for sorting a fulltext query on an index of Movie""" +input MovieFulltextSort { + score: SortDirection + movie: MovieSort +} + +"""The input for filtering the score of a fulltext search""" +input FloatWhere { + min: Float + max: Float +} +---- + +This query can then be used to perform a full-text query: + +[source, graphql, indent=0] +---- +query { + movieFulltextMovieTitle( + phrase: "Full Metal Jacket", + where: { score: min: 0.4 }, + sort: [{ movie: { title: ASC } }], + limit: 5, + offset: 10 + ) { + score + movies { + title + } + } +} +---- + +And thus return results in the following format: + +[source, json, indent=0] +---- +{ + "data": { + "movieFulltextMovieTitle": [ + { + "score": 0.44524085521698, + "movie": { + "title": "Full Moon High" + } + }, + { + "score": 1.411118507385254, + "movie": { + "title": "Full Metal Jacket" + } + } + ] + } +} +---- + +==== Argument changes + +The following changes have been made to `@fulltext` arguments: + +* `queryName` has been added to specify a custom name for the top-level query that is generated. +* `name` has been renamed to `indexName` to avoid ambiguity with the new `queryName` argument. + +These changes mean that the following type definition is now invalid: + +[source, graphql, indent=0] +---- +type Movie @fulltext(indexes: [{ name: "MovieTitle", fields: ["title"] }]) { + title: String! +} +---- + +The `name` argument now needs to be replaced with `indexName`: + +[source, graphql, indent=0] +---- +type Movie @fulltext(indexes: [{ indexName: "MovieTitle", fields: ["title"] }]) { + title: String! +} +---- + +As an example, the `queryName` argument can be used as: + +[source, graphql, indent=0] +---- +type Movie @fulltext(indexes: [{ queryName: "moviesByTitle", indexName: "MovieTitle", fields: ["title"] }]) { + title: String! +} +---- + +This means the top-level query is now `moviesByTitle` instead of `movieFulltextMovieTitle`: + +[source, graphql, indent=0] +---- +type Query { + moviesByTitle(phrase: String!, where: MovieFulltextWhere, sort: [MovieFulltextSort!], limit: Int, offset: Int): [MovieFulltextResult!]! +} +---- + +== Subscription options + +Subscriptions are no longer configured as a plugin, but as a feature within the `features` option. + +[cols="1,1"] +|=== +|Before | Now + +a| +[source, javascript] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, + plugins: { + subscriptions: plugin, + }, +}); +---- +a| +[source, javascript] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, + features: { + subscriptions: plugin, + }, +}); +---- +|=== + +=== Default subscriptions + +The class `Neo4jGraphQLSubscriptionsSingleInstancePlugin` is no longer exported. +Instead, the default subscriptions behavior can be enabled by setting the `subscriptions` option to `true` . + +[cols="1,1"] +|=== +|Before | Now + +a| +[source, javascript, indent=0] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, + plugin: { + subscriptions: new Neo4jGraphQLSubscriptionsSingleInstancePlugin(), + }, +}); +---- +a| +[source, javascript, indent=0] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, + features: { + subscriptions: true + }, +}); +---- +|=== + +=== Neo4j GraphQL subscriptions AMQP package + +The name of the interface underlying the subscriptions system has changed from `Neo4jGraphQLSubscriptionsPlugin` to `Neo4jGraphQLSubscriptionsEngine`. +If you were previously using the `@neo4j/graphql-plugins-subscriptions-amqp` package, this has been changed to `@neo4j/graphql-amqp-subscriptions-engine` to reflect this underlying change. + +To keep using it, uninstall the previous package and install the new one: + +[source, bash, indent=0] +---- +npm uninstall @neo4j/graphql-plugins-subscriptions-amqp +npm install @neo4j/graphql-amqp-subscriptions-engine +---- + +Then update any imports: + +[cols="1,1"] +|=== +|From | To + +a| +[source, javascript, indent=0] +---- +import { Neo4jGraphQLSubscriptionsAMQPPlugin } from "@neo4j/graphql-plugins-subscriptions-amqp"; +---- +a| +[source, javascript, indent=0] +---- +import { Neo4jGraphQLAMQPSubscriptionsEngine } from "@neo4j/graphql-amqp-subscriptions-engine"; +---- +|=== + +And change the instantiations: + +[cols="1,1"] +|=== +|From | To + +a| +[source, javascript, indent=0] +---- +const plugin = new Neo4jGraphQLSubscriptionsAMQPPlugin({ + connection: { + hostname: "localhost", + username: "guest", + password: "guest", + }, +}); +---- +a| +[source, javascript, indent=0] +---- +const subscriptionsEngine = new Neo4jGraphQLAMQPSubscriptionsEngine({ + connection: { + hostname: "localhost", + username: "guest", + password: "guest", + }, +}); +---- +|=== + +=== Custom subscription plugins + +The underlying subscription system has not changed. +Custom behavior can be implemented the same way, by creating a class implementing the interface described in xref::subscriptions/engines.adoc#custom-subscription[Subscriptions engines]. + +However, if using TypeScript, the exported interface to implement these classes has been renamed from `Neo4jGraphQLSubscriptionsPlugin` to `Neo4jGraphQLSubscriptionsEngine`. + +== Updated directives + +A number of directives and their arguments have been renamed in order to make using `@neo4j/graphql` more intuitive. +Here is a table with all the changes: + +[cols="1,2,2"] +|=== +|Before | Now | Example + +|`@alias` +|Properties in the alias directive are now automatically escaped using backticks. +If you were using backticks in the `property` argument of your `@alias` directives, you should now remove the escape strings as this is covered by the library. +a| +[source, graphql, indent=0] +---- +type User { + id: ID! @id + username: String! @alias(property: "dbUserName") +} +---- + +|`@callback` +|Renamed to `@populatedBy`. +Additionally, the `name` argument has been renamed to `callback` and it is still used to specify the callback used to populate the field's value. +a| +.Before +[source, graphql, indent=0] +---- +type User { + id: ID! @callback(name: "nanoid", operations: [CREATE]) + firstName: String! + surname: String! +} +---- + +.Now +[source, graphql, indent=0] +---- +new Neo4jGraphQL({ + typeDefs, + features: { // changed from config + populatedBy: { // changed from callback + callbacks: { + nanoid: () => { return nanoid(); } + } + } + } +}); +---- + +|`@computed` +a|Renamed to `@customResolver`. +Note that before and after these changes, a custom resolver needs to be defined as follows: + +[source, javascript, indent=0] +---- +new Neo4jGraphQL({ + typeDefs, + resolvers: { + User: { + fullName: ({ firstName, lastName }, args, context, info) => (`${firstName} ${lastName}`), + } + } +}); +---- + +a| +.Before +[source, graphql, indent=0] +---- +type User { + firstName: String! + lastName: String! + fullName: String! @computed(from: ["firstName", "lastName"]) +} +---- + +.Now +[source, graphql, indent=0] +---- +type User { + firstName: String! + lastName: String! + fullName: String! @customResolver(requires: ["firstName", "lastName"]) +} +---- + +|`from` +a| Renamed to `requires`. +In version 4.0.0, it is now possible to require non-scalar fields, which means it is also possible to require fields on related type. + +{nbsp} + +Additionally, the `requires` argument now accepts a GraphQL selection set instead of a list of strings and also validates the required selection set against your type definitions. +This means that if there is no field called `someFieldThatDoesNotExist`, an error would be thrown on startup if you tried to use the following type definitions: + +{nbsp} + +[source, graphql, indent=0] +---- +type User { + firstName: String! + lastName: String! + fullName: String! @customResolver(requires: "firstName someFieldThatDoesNotExist") +} +---- + +a| +.Before +[source, graphql, indent=0] +---- +type User { + firstName: String! + lastName: String! + fullName: String! @customResolver(requires: ["firstName", "lastName"]) +} +---- + +.Now +[source, graphql, indent=0] +---- +type User { + firstName: String! + lastName: String! + fullName: String! @customResolver(requires: "firstName lastName") +} +---- + +.Additional example +[source, graphql, indent=0] +---- +interface Publication { + publicationYear: Int! +} + +type Author { + name: String! + publications: [Publication!]! @relationship(type: "WROTE", direction: OUT) + publicationsWithAuthor: [String!]! + @customResolver( + requires: "name publications { publicationYear ...on Book { title } ... on Journal { subject } }" + ) +} + +type Book implements Publication { + title: String! + publicationYear: Int! + author: [Author!]! @relationship(type: "WROTE", direction: IN) +} + +type Journal implements Publication { + subject: String! + publicationYear: Int! + author: [Author!]! @relationship(type: "WROTE", direction: IN) +} +---- + +|`@exclude` +|Replaced by `@query`, `@mutation`, and `@subscription`. +These new directives allow for fully granular configuration for each operation. +a|* `@exclude` -> `@query(read: false, aggregate: false) @mutation(operations: []) @subscription(events: [])`. + +* `@exclude(operations: [READ])` -> `@query(read: false, aggregate: false)`. + +* `@exclude(operation: [CREATE, UPDATE, DELETE])` -> `@mutation(operations: [])`. + +|`@id` +a|Deprecated with _all_ of its arguments removed and/or replaced. +a|*`autogenerate` -> `@unique`* + +The default value was `true`. +If set to `false`, the `@id` directive was almost a no-op only used to manage a unique node property constraint. +Use the `@unique` directive instead. + +{nbsp} + +*`global` -> `@relayId`* + +This argument was used to configure the field that would form the global node identifier for Relay. +This functionality has been moved into its own directive, `@relayId`. The use of `@relayId` will ensure a unique node property constraint for the field. + +{nbsp} + +*`@id` -> `unique` + `@id`* + +The `@id` directive used to also manage unique node property constraints for a field. +This functionality has been removed. +Use the `@unique` directive in combination with `@id` if you want the field to be backed by a constraint. + +|`@plural` +|Removed from `@node` and replaced by the `@plural` directive. +It takes the pluralized type name using the `value` argument. +a| +.Invalid `plural` type definition +[source, graphql, indent=0] +---- +type Tech @node(label: "TechDB", plural: "Techs") { + name: String +} +---- + +.Updated version + +[source, graphql, indent=0] +---- +type Tech @node(label: "TechDB") @plural(value: "Techs") { + name: String +} +---- + +|`label` and `additionalLabels` +a|Removed from `@node` and replaced by `labels`. +It accepts a list of string labels that are used when a node of the given GraphQL type is created. + +{nbsp} + +Note that defining `labels` means taking control of the database labels of the node. +Indexes and constraints in Neo4j only support a single label, for which the first element of the `labels` argument will be used. + +{nbsp} + +As before, providing none of these arguments results in the node label being the same as the GraphQL type name. +This can cause implications on constraits. +For instance, in the case where unique constraint is asserted for the label `Tech` and the property `name`: + +{nbsp} + +[source, graphql, indent=0] +---- +type Tech @node(labels: ["Tech", "TechDB"]) { + name: String @unique +} +---- +a| +.Current equivalent to `label` +[source, graphql, indent=0] +---- +type Tech @node(label: "TechDB") { + name: String +} +# becomes +type Tech @node(labels: ["TechDB"]) { + name: String +} +---- + +.Current equivalent to `additionalLabels` +[source, graphql, indent=0] +---- +type Tech @node(additionalLabels: ["TechDB"]) { + name: String +} +# becomes +type Tech @node(labels: ["Tech", "TechDB"]) { + name: String +} +---- + +.Current equivalent to both arguments +[source, graphql, indent=0] +---- +type Tech @node(label: "TechDB", additionalLabels: ["AwesomeTech"]) { + name: String +} +# becomes +type Tech @node(labels: ["TechDB", "AwesomeTech"]) { + name: String +} +---- + +|`@queryOptions` and `limit` +| Removed and moved to `@limit`. +a| +.Outdated example +[source, graphql, indent=0] +---- +type Record @queryOptions(limit: { default: 10, max: 100 }) { + id: ID! +} +---- + +.Updated version using `@limit` +[source, graphql, indent=0] +---- +type Record @limit(default: 10, max: 100) { + id: ID! +} +---- + +|`@readonly` and `@writeonly` +|Removed and replaced by the `@selectable` and `@settable` directives. +They can be used to configure not only if fields are readable or writable, but also when they should be readable or writable. +a|* `@readonly` -> `@settable(onCreate: false, onUpdate: false)` +* `@writeonly` -> `@selectable(onRead: false, onAggregate: false)` + +|`@query` and `@relationship` +|Aggregation operations are no longer generated by default. +They can be enabled case by case using the directives xref::/schema-configuration/type-configuration.adoc#_query[`@query`] and xref::/schema-configuration/field-configuration.adoc#_relationship[`@relationship`]. +a| +[source, graphql, indent=0] +---- +type Movie { + title: String! +} + +type Actor @query(aggregate: true) { + name: String! + actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, aggregate: true) +} +---- +|=== + +[relationship-aggregate] +== Relationship updates + +Here are the changes and updates to `@relationship`-related features. + +[discrete] +=== *Relationship types are now automatically escaped* + +Relationship types are now automatically escaped. +If you have previously escaped your relationship types using backticks, you must now remove these as this is covered by the library. + +[discrete] +=== *`@relationshipProperties` now mandatory* + +Current changes require the distinction between interfaces that are used to specify relationship properties, and others. +Therefore, the `@relationshipProperties` directive is now required on all relationship property interfaces. +If it is not included, an error is thrown. + +As a result, in version 4.0.0, the following type definitions are invalid: + +[source, graphql, indent=0] +---- +type Person { + name: String! + movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedIn") +} + +type Movie { + title: String! + actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") +} + +interface ActedIn { + screenTime: Int! +} +---- + +`ActedIn` must be decorated with `@relationshipProperties`: + +[source, graphql, indent=0] +---- +interface ActedIn @relationshipProperties { + screenTime: Int! +} +---- + +[discrete] +=== Duplicate relationship fields are now checked for + +In 3.0.0, it was possible to define schemas with types that have multiple relationship fields connected by the same type of relationships. +Now, this kind of scenario is detected during schema generation and an error is thrown so developers are informed to fix the type definitions. + +Here is an example of what is now considered invalid with these checks: + +[source, graphql, indent=0] +---- +type Team { + player1: Person! @relationship(type: "PLAYS_IN", direction: IN) + player2: Person! @relationship(type: "PLAYS_IN", direction: IN) + backupPlayers: [Person!]! @relationship(type: "PLAYS_IN", direction: IN) +} + +type Person { + teams: [Team!]! @relationship(type: "PLAYS_IN", direction: OUT) +} +---- + +In this example, there are multiple fields in the `Team` type which have the same `Person` type, the same `@relationship` type and ("PLAYS_IN") direction (IN). This is an issue when returning data from the database, as there would be no difference between `player1`, `player2` and `backupPlayers`. Selecting these fields would then return the same data. + +These checks can be disabled by disabling all validation in the library, however, this is not recommended unless in production with 100% confidence of type definitions input. + +[source, javascript, indent=0] +---- +const neoSchema = new Neo4jGraphQL({ + typeDefs, + validate: false, +}); +---- + +== `cypherParams` + +In 3.0.0, `cypherParams` was available in the context to provide the ability to pass arbitrary parameters to a custom Cypher query. +This functionality remains in 4.0.0, but you no longer have to use the `$cypherParams` prefix to reference these parameters. diff --git a/modules/ROOT/pages/migration/ogm.adoc b/modules/ROOT/pages/migration/4.0.0/ogm.adoc similarity index 100% rename from modules/ROOT/pages/migration/ogm.adoc rename to modules/ROOT/pages/migration/4.0.0/ogm.adoc diff --git a/modules/ROOT/pages/migration/index.adoc b/modules/ROOT/pages/migration/index.adoc index a1ce65e4..6e0f6015 100644 --- a/modules/ROOT/pages/migration/index.adoc +++ b/modules/ROOT/pages/migration/index.adoc @@ -1,7 +1,7 @@ -[[migration-guide]] +[[v5-migration]] :description: This page lists the breaking changes from version 3.0.0 to 4.0.0 and describes how to update. -= Migration to 4.0.0 -:page-aliases: guides/index.adoc, guides/migration-guide/index.adoc, guides/migration-guide/server.adoc, guides/migration-guide/type-definitions.adoc, guides/migration-guide/mutations.adoc +:page-aliases: guides/v5-migration/index.adoc , migration/v5-migration/index.adoc += Migration to 5.0.0 This page lists all breaking changes from the Neo4j GraphQL Library version 3.x to 4.x and how to update it. diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..b8af31d1 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,1958 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@antora/asciidoc-loader@3.1.7": + version "3.1.7" + resolved "https://registry.yarnpkg.com/@antora/asciidoc-loader/-/asciidoc-loader-3.1.7.tgz#86b1e278333b193ace457fed15c90ec1af416cdd" + integrity sha512-sM/poPtAi8Cx0g2oLGHoEYjTdE9pvIYLgbHW07fGf6c9wQYMd2DMsevtbhNKWp+xqxj/QinToz4JOaNLoy1nfg== + dependencies: + "@antora/logger" "3.1.7" + "@antora/user-require-helper" "~2.0" + "@asciidoctor/core" "~2.2" + +"@antora/cli@^3.1.0": + version "3.1.7" + resolved "https://registry.yarnpkg.com/@antora/cli/-/cli-3.1.7.tgz#8d80d83328793281f8f0b525020e761115accb49" + integrity sha512-yHo30VmiLLsZU4JW8VZR6fql9m5lIxocA2d0tduKQ+r4YSD1kt+bbwX3You3iMwW7oLoNo62zfU76F8CQBnm2g== + dependencies: + "@antora/logger" "3.1.7" + "@antora/playbook-builder" "3.1.7" + "@antora/user-require-helper" "~2.0" + commander "~10.0" + +"@antora/content-aggregator@3.1.7": + version "3.1.7" + resolved "https://registry.yarnpkg.com/@antora/content-aggregator/-/content-aggregator-3.1.7.tgz#7458f428afcbf2bc7b63014349b1305cd14ea60c" + integrity sha512-2gBbxaDxqY4QRw9Vut0IbKNqI9zAAohxjT0zcA5Am10kE3ywvzXaBa3tvb+A7vUl/vRcCC0LPM7pO37RVrbsGA== + dependencies: + "@antora/expand-path-helper" "~2.0" + "@antora/logger" "3.1.7" + "@antora/user-require-helper" "~2.0" + braces "~3.0" + cache-directory "~2.0" + glob-stream "~7.0" + hpagent "~1.2" + isomorphic-git "~1.25" + js-yaml "~4.1" + multi-progress "~4.0" + picomatch "~2.3" + progress "~2.0" + should-proxy "~1.0" + simple-get "~4.0" + vinyl "~2.2" + +"@antora/content-classifier@3.1.7": + version "3.1.7" + resolved "https://registry.yarnpkg.com/@antora/content-classifier/-/content-classifier-3.1.7.tgz#fee84c32e07cb0ea82a1902ba2c0cf02ce995095" + integrity sha512-94XwJ35pkWJU6dJqQg5oreJRCkaXwU6yw9XSyB731o4i/0romkazKnu7deHugqdgvzqn92AlMRG6R0clhJLEsw== + dependencies: + "@antora/asciidoc-loader" "3.1.7" + "@antora/logger" "3.1.7" + mime-types "~2.1" + vinyl "~2.2" + +"@antora/document-converter@3.1.7": + version "3.1.7" + resolved "https://registry.yarnpkg.com/@antora/document-converter/-/document-converter-3.1.7.tgz#6c0d4147a411d58ea129c143cc029bb667e16baa" + integrity sha512-cRVJf7QyclxjWbA0gWz7hncZYThIREikkwaxaa26LsRCfBNlw7wxs7lWejvIvEl1LVshupbinJwKUPPQPOsHhw== + dependencies: + "@antora/asciidoc-loader" "3.1.7" + +"@antora/expand-path-helper@~2.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@antora/expand-path-helper/-/expand-path-helper-2.0.0.tgz#2e7e8fe35ed97049a8f5f971a63a7a75886e646d" + integrity sha512-CSMBGC+tI21VS2kGW3PV7T2kQTM5eT3f2GTPVLttwaNYbNxDve08en/huzszHJfxo11CcEs26Ostr0F2c1QqeA== + +"@antora/file-publisher@3.1.7": + version "3.1.7" + resolved "https://registry.yarnpkg.com/@antora/file-publisher/-/file-publisher-3.1.7.tgz#a2da1127812f11e409bd13f53001fabd700075a6" + integrity sha512-UH2o0DJuv9BJvWgn+QSE3MhtHB3oN3lG5lJkV3fQi1jAV+qPIHoiTIYhbw02mj5KQ3Qbt7YWWAKZKuGl3rEdjg== + dependencies: + "@antora/expand-path-helper" "~2.0" + "@antora/user-require-helper" "~2.0" + "@vscode/gulp-vinyl-zip" "~2.5" + vinyl "~2.2" + vinyl-fs "~3.0" + +"@antora/logger@3.1.7": + version "3.1.7" + resolved "https://registry.yarnpkg.com/@antora/logger/-/logger-3.1.7.tgz#323d76222c49805076b2e06b7f428175d9ff8dd8" + integrity sha512-Z2tfNIi9G4BnAZq26Kp30974FxCVCtvH46FOi6ClnkJg6Uf2gTrVlJERmtsDTsHjWsf1qKbnj/4b99/AU31iQg== + dependencies: + "@antora/expand-path-helper" "~2.0" + pino "~8.14" + pino-pretty "~10.0" + sonic-boom "~3.3" + +"@antora/navigation-builder@3.1.7": + version "3.1.7" + resolved "https://registry.yarnpkg.com/@antora/navigation-builder/-/navigation-builder-3.1.7.tgz#cfab567fc15e82cb12e14171cb20eac51dc0a3bc" + integrity sha512-QvMPb0qY1zfgyLCfuEhJOpO5qSVjaVe5X/bQjSii9vDGgpIEiC2yt/hgqER37E/3zsBGEZvCH5lSLk1c7x0+EQ== + dependencies: + "@antora/asciidoc-loader" "3.1.7" + +"@antora/page-composer@3.1.7": + version "3.1.7" + resolved "https://registry.yarnpkg.com/@antora/page-composer/-/page-composer-3.1.7.tgz#fb7909df287b3d42ce655025b9f979e97b86e97b" + integrity sha512-zJMzYznPT6Vd59nEXio/2rolkX070Nup6g4a8d4RCz0WoE8dmMidw6XFgjAHr0Lyh14/FHgBPlYXfhkDFR16Mw== + dependencies: + "@antora/logger" "3.1.7" + handlebars "~4.7" + require-from-string "~2.0" + +"@antora/playbook-builder@3.1.7": + version "3.1.7" + resolved "https://registry.yarnpkg.com/@antora/playbook-builder/-/playbook-builder-3.1.7.tgz#1f47399faa9a44cd08fde8a383b2b26d3c00179b" + integrity sha512-lU80S1BqUy9DvqziEzRwpYTaWhOshxgrGAjf/F5VjAIaHCGVx0rZgfoI2rgFFkbVaH94kauOngdtTXDPXC1fPQ== + dependencies: + "@iarna/toml" "~2.2" + convict "~6.2" + js-yaml "~4.1" + json5 "~2.2" + +"@antora/redirect-producer@3.1.7": + version "3.1.7" + resolved "https://registry.yarnpkg.com/@antora/redirect-producer/-/redirect-producer-3.1.7.tgz#0d61f283492922404a99b75b94d4657ff5569f48" + integrity sha512-6zAHfcOb0v0829nAbn/3HMilbactjbjU7zBT9Iy6JHQfbqXT/g/mUT9N13Lj/wbq/nm0qKQJweB0Mi6BS2fbMw== + dependencies: + vinyl "~2.2" + +"@antora/site-generator-default@^3.1.0": + version "3.1.7" + resolved "https://registry.yarnpkg.com/@antora/site-generator-default/-/site-generator-default-3.1.7.tgz#745f28030922b621c409f698d09a1956503b4237" + integrity sha512-m9UbejttKzp8MKJTEc+aKXi5SNb864QO7lQiQzSR0fiWnIR8WIM73CPPwkVeOXdKqaJvQp5IF9rlXXTkkC19fw== + dependencies: + "@antora/site-generator" "3.1.7" + +"@antora/site-generator@3.1.7": + version "3.1.7" + resolved "https://registry.yarnpkg.com/@antora/site-generator/-/site-generator-3.1.7.tgz#0f12dab5e6a5784ff1b58527ed1b357880d6d7ba" + integrity sha512-39KWip9bLdQ+4ssyqjI+O0COquKHxoeznxY2/3w5pNZEoeTg8cD7kOsnWfbocw0R3Rj+kJV5MnqICFNq0nuPeA== + dependencies: + "@antora/asciidoc-loader" "3.1.7" + "@antora/content-aggregator" "3.1.7" + "@antora/content-classifier" "3.1.7" + "@antora/document-converter" "3.1.7" + "@antora/file-publisher" "3.1.7" + "@antora/logger" "3.1.7" + "@antora/navigation-builder" "3.1.7" + "@antora/page-composer" "3.1.7" + "@antora/playbook-builder" "3.1.7" + "@antora/redirect-producer" "3.1.7" + "@antora/site-mapper" "3.1.7" + "@antora/site-publisher" "3.1.7" + "@antora/ui-loader" "3.1.7" + "@antora/user-require-helper" "~2.0" + +"@antora/site-mapper@3.1.7": + version "3.1.7" + resolved "https://registry.yarnpkg.com/@antora/site-mapper/-/site-mapper-3.1.7.tgz#4cf9b33c107162e51338e0f1caa50d3555cdaad4" + integrity sha512-x++89btbwk8FxyU2+H/RHQMnsC9sdvQvXcwXwNt11eXN1qj7t8TPiQZTalg7gkf0/osY4l7JRpGBY5JJfOJVig== + dependencies: + "@antora/content-classifier" "3.1.7" + vinyl "~2.2" + +"@antora/site-publisher@3.1.7": + version "3.1.7" + resolved "https://registry.yarnpkg.com/@antora/site-publisher/-/site-publisher-3.1.7.tgz#0de42f80a581cd6391c4e4de152c9e73aa864510" + integrity sha512-zHaJc7UeBfFSIhBbQ5U5Ud4u671M84oqSJb3pPxlUiJDP7iVJlSl+0GNm0NAIoDizjPtVksUI88fzqCy5rfSUQ== + dependencies: + "@antora/file-publisher" "3.1.7" + +"@antora/ui-loader@3.1.7": + version "3.1.7" + resolved "https://registry.yarnpkg.com/@antora/ui-loader/-/ui-loader-3.1.7.tgz#09a7f2e3917fdc0dbdeba76f4199b0e7792bfe39" + integrity sha512-79QuZB0c91dveoESa3RcE18ZZFJo0rDZX9aJKHVc20dInQBGCgfURPqB2OytkzaXD3lNtDJ41yjKNYZqsAQy1Q== + dependencies: + "@antora/expand-path-helper" "~2.0" + "@vscode/gulp-vinyl-zip" "~2.5" + braces "~3.0" + cache-directory "~2.0" + glob-stream "~7.0" + hpagent "~1.2" + js-yaml "~4.1" + picomatch "~2.3" + should-proxy "~1.0" + simple-get "~4.0" + vinyl "~2.2" + +"@antora/user-require-helper@~2.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@antora/user-require-helper/-/user-require-helper-2.0.0.tgz#7ba08253989cee86c4877a909278013f7fd383af" + integrity sha512-5fMfBZfw4zLoFdDAPMQX6Frik90uvfD8rXOA4UpXPOUikkX4uT1Rk6m0/4oi8oS3fcjiIl0k/7Nc+eTxW5TcQQ== + dependencies: + "@antora/expand-path-helper" "~2.0" + +"@asciidoctor/core@~2.2": + version "2.2.6" + resolved "https://registry.yarnpkg.com/@asciidoctor/core/-/core-2.2.6.tgz#a59a9e8ab48ac0a615d5a3200214d3071291c5d5" + integrity sha512-TmB2K5UfpDpSbCNBBntXzKHcAk2EA3/P68jmWvmJvglVUdkO9V6kTAuXVe12+h6C4GK0ndwuCrHHtEVcL5t6pQ== + dependencies: + asciidoctor-opal-runtime "0.3.3" + unxhr "1.0.1" + +"@iarna/toml@~2.2": + version "2.2.5" + resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c" + integrity sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg== + +"@neo4j-antora/aliases-redirects@^0.2.1": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@neo4j-antora/aliases-redirects/-/aliases-redirects-0.2.3.tgz#4aec337a6b8078c750b7c0ab219d8c13b643215a" + integrity sha512-r5XB4FHnMFYg2aPjpzdmyBwjKLiCcgIlmF5V6CzzgJKC06qxXPZmmQzVDsx3MS2PVRXs0rPRBjRFUqC++4Z8Xg== + +"@neo4j-antora/antora-add-notes@^0.1.6": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@neo4j-antora/antora-add-notes/-/antora-add-notes-0.1.6.tgz#b6b70a8a6807124fb9795da12afae7f36b1a2963" + integrity sha512-B06tnV2TBAA5D6DkjByxnOvwcyZ/+3lRXf/0nIAxqnfo/XBjQVsw00X8xQ4gk9sbAnT1KndAdQtW03kG6fTgng== + +"@neo4j-antora/antora-modify-sitemaps@^0.4.3": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@neo4j-antora/antora-modify-sitemaps/-/antora-modify-sitemaps-0.4.4.tgz#29bec674ab46da70776fb2943d9f0258c0cf8049" + integrity sha512-IkXoilOJquZPB5G5ZhrgfSN6U3E2YToWakehtF55RA+CNQS0KboTAB2vUH01+Tkmkd8K6UElf41A6cGnnrvs0g== + +"@neo4j-antora/antora-page-roles@^0.3.1": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@neo4j-antora/antora-page-roles/-/antora-page-roles-0.3.2.tgz#222a4002b42128e0e4e6e6fd7413e8f59ce65be2" + integrity sha512-RqmMHcTyM87lJAhjSPdkoUzFxQCjsM2N4+ryVK4GIahAJyNV9OYydBrsjGrDbIh6F4YS+LWG+SXyvLD2zJ1keQ== + +"@neo4j-antora/antora-table-footnotes@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@neo4j-antora/antora-table-footnotes/-/antora-table-footnotes-0.3.2.tgz#598a87aa15fd3dcaf22b07dd7d8a7c7f572ce251" + integrity sha512-DXEGVHMJumoKiY/ZCaGRTXl2OhPziPCHT+arj18TmpU50sUs+hyjOPuTkUXUvBwNZwm109Nm1PJPvKLVIJCZSg== + +"@neo4j-documentation/macros@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@neo4j-documentation/macros/-/macros-1.0.2.tgz#5d73dacbcd524dce656f097f9cd73779461b3ba0" + integrity sha512-83w4HPxt9lx1cR6w/Zi741Fu8/IE8pAGHf3BxIvBE8M+XzdT1f1Y6EpbLFrdZjqKwlcomdhks1/+zxlAbVa1xg== + +"@neo4j-documentation/remote-include@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@neo4j-documentation/remote-include/-/remote-include-1.0.0.tgz#fda7f94e400165d5de83020189fad24a5fe051a9" + integrity sha512-SprNp9XsWiMBC0T44vs3JUwEYhoyJlg+du5kP0f9RGewXrSeEgsr5tY7nQDa4Bou9iG0sBl0+2u4XZjiVMkiuw== + +"@vscode/gulp-vinyl-zip@~2.5": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@vscode/gulp-vinyl-zip/-/gulp-vinyl-zip-2.5.0.tgz#98ace3f66f29f32b9423474095f4d20121046598" + integrity sha512-PP/xkOoLBSY3V04HmzRxF+NOxkRJ/m2D0YwWpfx1FCFv5G8+sZUGPvxX+LRgdJ5vQcR1RHck5x1IkHi75Qjdbw== + dependencies: + queue "^4.2.1" + through "^2.3.8" + through2 "^2.0.3" + vinyl "^2.0.2" + vinyl-fs "^3.0.3" + yauzl "^2.2.1" + yazl "^2.2.1" + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + +accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +append-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/append-buffer/-/append-buffer-1.0.2.tgz#d8220cf466081525efea50614f3de6514dfa58f1" + integrity sha512-WLbYiXzD3y/ATLZFufV/rZvWdZOs+Z/+5v1rBZ463Jn398pa6kcde27cvozYnBoxXblGZTFfoPpsaEw0orU5BA== + dependencies: + buffer-equal "^1.0.0" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +asciidoctor-opal-runtime@0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/asciidoctor-opal-runtime/-/asciidoctor-opal-runtime-0.3.3.tgz#2667635f858d3eb3fdfcf6795cf68138e2040174" + integrity sha512-/CEVNiOia8E5BMO9FLooo+Kv18K4+4JBFRJp8vUy/N5dMRAg+fRNV4HA+o6aoSC79jVU/aT5XvUpxSxSsTS8FQ== + dependencies: + glob "7.1.3" + unxhr "1.0.1" + +async-lock@^1.1.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.4.1.tgz#56b8718915a9b68b10fce2f2a9a3dddf765ef53f" + integrity sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ== + +atomic-sleep@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" + integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +body-parser@1.20.1: + version "1.20.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" + integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== + dependencies: + bytes "3.1.2" + content-type "~1.0.4" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.1" + type-is "~1.6.18" + unpipe "1.0.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@~3.0, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + +buffer-equal@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.1.tgz#2f7651be5b1b3f057fcd6e7ee16cf34767077d90" + integrity sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg== + +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +cache-directory@~2.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/cache-directory/-/cache-directory-2.0.0.tgz#0d8efa1abbb6d1dd926d255ce733b4f7c5ab2892" + integrity sha512-7YKEapH+2Uikde8hySyfobXBqPKULDyHNl/lhKm7cKf/GJFdG/tU/WpLrOg2y9aUrQrWUilYqawFIiGJPS6gDA== + dependencies: + xdg-basedir "^3.0.0" + +call-bind@^1.0.0, call-bind@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513" + integrity sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ== + dependencies: + function-bind "^1.1.2" + get-intrinsic "^1.2.1" + set-function-length "^1.1.1" + +chokidar@^3.5.2: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +clean-git-ref@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/clean-git-ref/-/clean-git-ref-2.0.1.tgz#dcc0ca093b90e527e67adb5a5e55b1af6816dcd9" + integrity sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw== + +clone-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" + integrity sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g== + +clone-stats@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" + integrity sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag== + +clone@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== + +cloneable-readable@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.1.3.tgz#120a00cb053bfb63a222e709f9683ea2e11d8cec" + integrity sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ== + dependencies: + inherits "^2.0.1" + process-nextick-args "^2.0.0" + readable-stream "^2.3.5" + +colorette@^2.0.7: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +commander@~10.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +convert-source-map@^1.5.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +convict@~6.2: + version "6.2.4" + resolved "https://registry.yarnpkg.com/convict/-/convict-6.2.4.tgz#be290672bf6397eec808d3b11fc5f71785b02a4b" + integrity sha512-qN60BAwdMVdofckX7AlohVJ2x9UvjTNoKVXCL2LxFk1l7757EJqf1nySdMkPQer0bt8kQ5lQiyZ9/2NvrFBuwQ== + dependencies: + lodash.clonedeep "^4.5.0" + yargs-parser "^20.2.7" + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +crc-32@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" + integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== + +dateformat@^4.6.3: + version "4.6.3" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5" + integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA== + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + +define-data-property@^1.0.1, define-data-property@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.1.tgz#c35f7cd0ab09883480d12ac5cb213715587800b3" + integrity sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ== + dependencies: + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + +define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +diff3@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/diff3/-/diff3-0.0.3.tgz#d4e5c3a4cdf4e5fe1211ab42e693fcb4321580fc" + integrity sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g== + +duplexify@^3.6.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" + integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== + dependencies: + end-of-stream "^1.0.0" + inherits "^2.0.1" + readable-stream "^2.0.0" + stream-shift "^1.0.0" + +duplexify@^4.1.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0" + integrity sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw== + dependencies: + end-of-stream "^1.4.1" + inherits "^2.0.3" + readable-stream "^3.1.1" + stream-shift "^1.0.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + +events@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +express@^4.18.1: + version "4.18.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" + integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.1" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.5.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.11.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +extend@^3.0.0, extend@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +fast-copy@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-3.0.1.tgz#9e89ef498b8c04c1cd76b33b8e14271658a732aa" + integrity sha512-Knr7NOtK3HWRYGtHoJrjkaWepqT8thIVGAwt0p0aUs1zqkAzXZV4vo9fFNwyb5fcqK1GKYFYxldQdIDVKhUAfA== + +fast-redact@^3.1.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.3.0.tgz#7c83ce3a7be4898241a46560d51de10f653f7634" + integrity sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ== + +fast-safe-stringify@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" + integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== + +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== + dependencies: + pend "~1.2.0" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +flush-write-stream@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" + integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== + dependencies: + inherits "^2.0.3" + readable-stream "^2.3.6" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fs-mkdirp-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz#0b7815fc3201c6a69e14db98ce098c16935259eb" + integrity sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ== + dependencies: + graceful-fs "^4.1.11" + through2 "^2.0.3" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b" + integrity sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA== + dependencies: + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + integrity sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA== + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-stream@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-6.1.0.tgz#7045c99413b3eb94888d83ab46d0b404cc7bdde4" + integrity sha512-uMbLGAP3S2aDOHUDfdoYcdIePUCfysbAd0IAoWVZbeGU/oNQ8asHVSshLDJUPWxfzj8zsCG7/XeHPHTtow0nsw== + dependencies: + extend "^3.0.0" + glob "^7.1.1" + glob-parent "^3.1.0" + is-negated-glob "^1.0.0" + ordered-read-streams "^1.0.0" + pumpify "^1.3.5" + readable-stream "^2.1.5" + remove-trailing-separator "^1.0.1" + to-absolute-glob "^2.0.0" + unique-stream "^2.0.2" + +glob-stream@~7.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-7.0.0.tgz#1c42227d5c0bc1aba20f36ee9b09cef7f194dc00" + integrity sha512-evR4kvr6s0Yo5t4CD4H171n4T8XcnPFznvsbeN8K9FPzc0Q0wYqcOWyGtck2qcvJSLXKnU6DnDyfmbDDabYvRQ== + dependencies: + extend "^3.0.2" + glob "^7.2.0" + glob-parent "^6.0.2" + is-negated-glob "^1.0.0" + ordered-read-streams "^1.0.1" + pumpify "^2.0.1" + readable-stream "^3.6.0" + remove-trailing-separator "^1.1.0" + to-absolute-glob "^2.0.2" + unique-stream "^2.3.1" + +glob@7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.1.1, glob@^7.2.0: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^8.0.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.6: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +handlebars@~4.7: + version "4.7.8" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" + integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.2" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz#52ba30b6c5ec87fd89fa574bc1c39125c6f65340" + integrity sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg== + dependencies: + get-intrinsic "^1.2.2" + +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +hasown@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" + integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== + dependencies: + function-bind "^1.1.2" + +help-me@^4.0.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/help-me/-/help-me-4.2.0.tgz#50712bfd799ff1854ae1d312c36eafcea85b0563" + integrity sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA== + dependencies: + glob "^8.0.0" + readable-stream "^3.6.0" + +hpagent@~1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/hpagent/-/hpagent-1.2.0.tgz#0ae417895430eb3770c03443456b8d90ca464903" + integrity sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA== + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== + +ignore@^5.1.4: + version "5.3.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78" + integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-absolute@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" + integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== + dependencies: + is-relative "^1.0.0" + is-windows "^1.0.1" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-extglob@^2.1.0, is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + integrity sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw== + dependencies: + is-extglob "^2.1.0" + +is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-negated-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" + integrity sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-relative@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" + integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== + dependencies: + is-unc-path "^1.0.0" + +is-unc-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" + integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== + dependencies: + unc-path-regex "^0.1.2" + +is-utf8@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + integrity sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q== + +is-valid-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa" + integrity sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA== + +is-windows@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isomorphic-git@~1.25: + version "1.25.3" + resolved "https://registry.yarnpkg.com/isomorphic-git/-/isomorphic-git-1.25.3.tgz#e8e7d6981bec2aa2b7b2c78f501ae49755a0c025" + integrity sha512-iUaDB5kObupWpwjQ+EGkDQmaYQzbq1XaPqo+xJCCfbPViGZL94rU2RvK4EDJKpYbyiwbq5OSLEjkXHa8r5I1aw== + dependencies: + async-lock "^1.1.0" + clean-git-ref "^2.0.1" + crc-32 "^1.2.0" + diff3 "0.0.3" + ignore "^5.1.4" + minimisted "^2.0.0" + pako "^1.0.10" + pify "^4.0.1" + readable-stream "^3.4.0" + sha.js "^2.4.9" + simple-get "^4.0.1" + +joycon@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" + integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== + +js-yaml@~4.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@~2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +lazystream@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638" + integrity sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw== + dependencies: + readable-stream "^2.0.5" + +lead@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lead/-/lead-1.0.0.tgz#6f14f99a37be3a9dd784f5495690e5903466ee42" + integrity sha512-IpSVCk9AYvLHo5ctcIXxOBpMWUe+4TKN3VPWAKUbJikkmsGp0VrSM8IttVc32D6J4WUsiPE6aEFRNmIoF/gdow== + dependencies: + flush-write-stream "^1.0.2" + +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@~2.1, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.5, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +minimisted@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/minimisted/-/minimisted-2.0.1.tgz#d059fb905beecf0774bc3b308468699709805cb1" + integrity sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA== + dependencies: + minimist "^1.2.5" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.3, ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +multi-progress@~4.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/multi-progress/-/multi-progress-4.0.0.tgz#a14dd4e4da14f6a7cc2e1a5c0abd8b005dd23923" + integrity sha512-9zcjyOou3FFCKPXsmkbC3ethv51SFPoA4dJD6TscIp2pUmy26kBDZW6h9XofPELrzseSkuD7r0V+emGEeo39Pg== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +nodemon@^2.0.19: + version "2.0.22" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.22.tgz#182c45c3a78da486f673d6c1702e00728daf5258" + integrity sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ== + dependencies: + chokidar "^3.5.2" + debug "^3.2.7" + ignore-by-default "^1.0.1" + minimatch "^3.1.2" + pstree.remy "^1.1.8" + semver "^5.7.1" + simple-update-notifier "^1.0.7" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.5" + +nopt@~1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" + integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg== + dependencies: + abbrev "1" + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w== + dependencies: + remove-trailing-separator "^1.0.1" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +now-and-later@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.1.tgz#8e579c8685764a7cc02cb680380e94f43ccb1f7c" + integrity sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ== + dependencies: + once "^1.3.2" + +object-inspect@^1.9.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" + integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.0.4: + version "4.1.5" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" + integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== + dependencies: + call-bind "^1.0.5" + define-properties "^1.2.1" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +on-exit-leak-free@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz#fed195c9ebddb7d9e4c3842f93f281ac8dadd3b8" + integrity sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA== + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +ordered-read-streams@^1.0.0, ordered-read-streams@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e" + integrity sha512-Z87aSjx3r5c0ZB7bcJqIgIRX5bxR7A4aSzvIbaxd0oTkWBCOoKfuGHiKj60CHVUgg1Phm5yMZzBdt8XqRs73Mw== + dependencies: + readable-stream "^2.0.1" + +pako@^1.0.10: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + integrity sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@~2.3: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +pino-abstract-transport@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz#083d98f966262164504afb989bccd05f665937a8" + integrity sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA== + dependencies: + readable-stream "^4.0.0" + split2 "^4.0.0" + +pino-abstract-transport@v1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz#cc0d6955fffcadb91b7b49ef220a6cc111d48bb3" + integrity sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA== + dependencies: + readable-stream "^4.0.0" + split2 "^4.0.0" + +pino-pretty@~10.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/pino-pretty/-/pino-pretty-10.0.1.tgz#54a11182068949ff3069f1f7857f297a14926e58" + integrity sha512-yrn00+jNpkvZX/NrPVCPIVHAfTDy3ahF0PND9tKqZk4j9s+loK8dpzrJj4dGb7i+WLuR50ussuTAiWoMWU+qeA== + dependencies: + colorette "^2.0.7" + dateformat "^4.6.3" + fast-copy "^3.0.0" + fast-safe-stringify "^2.1.1" + help-me "^4.0.1" + joycon "^3.1.1" + minimist "^1.2.6" + on-exit-leak-free "^2.1.0" + pino-abstract-transport "^1.0.0" + pump "^3.0.0" + readable-stream "^4.0.0" + secure-json-parse "^2.4.0" + sonic-boom "^3.0.0" + strip-json-comments "^3.1.1" + +pino-std-serializers@^6.0.0: + version "6.2.2" + resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz#d9a9b5f2b9a402486a5fc4db0a737570a860aab3" + integrity sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA== + +pino@~8.14: + version "8.14.2" + resolved "https://registry.yarnpkg.com/pino/-/pino-8.14.2.tgz#99148b3400527fec168691044ba367cc7b7cd605" + integrity sha512-zKu9aWeSWTy1JgvxIpZveJKKsAr4+6uNMZ0Vf0KRwzl/UNZA3XjHiIl/0WwqLMkDwuHuDkT5xAgPA2jpKq4whA== + dependencies: + atomic-sleep "^1.0.0" + fast-redact "^3.1.1" + on-exit-leak-free "^2.1.0" + pino-abstract-transport v1.0.0 + pino-std-serializers "^6.0.0" + process-warning "^2.0.0" + quick-format-unescaped "^4.0.3" + real-require "^0.2.0" + safe-stable-stringify "^2.3.1" + sonic-boom "^3.1.0" + thread-stream "^2.0.0" + +process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +process-warning@^2.0.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-2.3.2.tgz#70d8a3251aab0eafe3a595d8ae2c5d2277f096a5" + integrity sha512-n9wh8tvBe5sFmsqlg+XQhaQLumwpqoAUruLwjCopgTmUBjJ/fjtBsJzKleCaIGBOMXYEhp1YfKl4d7rJ5ZKJGA== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + +progress@~2.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +pstree.remy@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== + +pump@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" + integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pumpify@^1.3.5: + version "1.5.1" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" + integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== + dependencies: + duplexify "^3.6.0" + inherits "^2.0.3" + pump "^2.0.0" + +pumpify@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-2.0.1.tgz#abfc7b5a621307c728b551decbbefb51f0e4aa1e" + integrity sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw== + dependencies: + duplexify "^4.1.1" + inherits "^2.0.3" + pump "^3.0.0" + +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + +queue@^4.2.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/queue/-/queue-4.5.1.tgz#6e4290a2d7e99dc75b34494431633fe5437b0dac" + integrity sha512-AMD7w5hRXcFSb8s9u38acBZ+309u6GsiibP4/0YacJeaurRshogB7v/ZcVPxP5gD5+zIw6ixRHdutiYUJfwKHw== + dependencies: + inherits "~2.0.0" + +quick-format-unescaped@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" + integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" + integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.5, readable-stream@^2.1.5, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readable-stream@^4.0.0: + version "4.5.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.5.2.tgz#9e7fc4c45099baeed934bff6eb97ba6cf2729e09" + integrity sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g== + dependencies: + abort-controller "^3.0.0" + buffer "^6.0.3" + events "^3.3.0" + process "^0.11.10" + string_decoder "^1.3.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +real-require@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78" + integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== + +remove-bom-buffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz#c2bf1e377520d324f623892e33c10cac2c252b53" + integrity sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ== + dependencies: + is-buffer "^1.1.5" + is-utf8 "^0.2.1" + +remove-bom-stream@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz#05f1a593f16e42e1fb90ebf59de8e569525f9523" + integrity sha512-wigO8/O08XHb8YPzpDDT+QmRANfW6vLqxfaXm1YXhnFf3AkSLyjfG3GEFg4McZkmgL7KvCj5u2KczkvSP6NfHA== + dependencies: + remove-bom-buffer "^3.0.0" + safe-buffer "^5.1.0" + through2 "^2.0.3" + +remove-trailing-separator@^1.0.1, remove-trailing-separator@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw== + +replace-ext@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.1.tgz#2d6d996d04a15855d967443631dd5f77825b016a" + integrity sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw== + +require-from-string@~2.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +resolve-options@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-1.1.0.tgz#32bb9e39c06d67338dc9378c0d6d6074566ad131" + integrity sha512-NYDgziiroVeDC29xq7bp/CacZERYsA9bXYd1ZmcJlF3BcrZv5pTb4NG7SjdyKDnXZ84aC4vo2u6sNKIA1LCu/A== + dependencies: + value-or-function "^3.0.0" + +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-stable-stringify@^2.3.1: + version "2.4.3" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" + integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +secure-json-parse@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz#5a5f9cd6ae47df23dba3151edd06855d47e09862" + integrity sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw== + +semver@^5.7.1: + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + +semver@~7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== + +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +set-function-length@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.0.tgz#2f81dc6c16c7059bda5ab7c82c11f03a515ed8e1" + integrity sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w== + dependencies: + define-data-property "^1.1.1" + function-bind "^1.1.2" + get-intrinsic "^1.2.2" + gopd "^1.0.1" + has-property-descriptors "^1.0.1" + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +sha.js@^2.4.9: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +should-proxy@~1.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/should-proxy/-/should-proxy-1.0.4.tgz#c805a501abf69539600634809e62fbf238ba35e4" + integrity sha512-RPQhIndEIVUCjkfkQ6rs6sOR6pkxJWCNdxtfG5pP0RVgUYbK5911kLTF0TNcCC0G3YCGd492rMollFT2aTd9iQ== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^4.0.1, simple-get@~4.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + +simple-update-notifier@^1.0.7: + version "1.1.0" + resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz#67694c121de354af592b347cdba798463ed49c82" + integrity sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg== + dependencies: + semver "~7.0.0" + +sonic-boom@^3.0.0, sonic-boom@^3.1.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-3.8.0.tgz#e442c5c23165df897d77c3c14ef3ca40dec66a66" + integrity sha512-ybz6OYOUjoQQCQ/i4LU8kaToD8ACtYP+Cj5qd2AO36bwbdewxWJ3ArmJ2cr6AvxlL2o0PqnCcPGUgkILbfkaCA== + dependencies: + atomic-sleep "^1.0.0" + +sonic-boom@~3.3: + version "3.3.0" + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-3.3.0.tgz#cffab6dafee3b2bcb88d08d589394198bee1838c" + integrity sha512-LYxp34KlZ1a2Jb8ZQgFCK3niIHzibdwtwNUWKg0qQRzsDoJ3Gfgkf8KdBTFU3SkejDEIlWwnSnpVdOZIhFMl/g== + dependencies: + atomic-sleep "^1.0.0" + +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +split2@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" + integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +stream-shift@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.3.tgz#85b8fab4d71010fc3ba8772e8046cc49b8a3864b" + integrity sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ== + +string_decoder@^1.1.1, string_decoder@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +thread-stream@^2.0.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.4.1.tgz#6d588b14f0546e59d3f306614f044bc01ce43351" + integrity sha512-d/Ex2iWd1whipbT681JmTINKw0ZwOUBZm7+Gjs64DHuX34mmw8vJL2bFAaNacaW72zYiTJxSHi5abUuOi5nsfg== + dependencies: + real-require "^0.2.0" + +through2-filter@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254" + integrity sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA== + dependencies: + through2 "~2.0.0" + xtend "~4.0.0" + +through2@^2.0.0, through2@^2.0.3, through2@~2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +to-absolute-glob@^2.0.0, to-absolute-glob@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1865f43d9e74b0822db9f145b78cff7d0f7c849b" + integrity sha512-rtwLUQEwT8ZeKQbyFJyomBRYXyE16U5VKuy0ftxLMK/PZb2fkOsg5r9kHdauuVDbsNdIBoC/HCthpidamQFXYA== + dependencies: + is-absolute "^1.0.0" + is-negated-glob "^1.0.0" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +to-through@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-through/-/to-through-2.0.0.tgz#fc92adaba072647bc0b67d6b03664aa195093af6" + integrity sha512-+QIz37Ly7acM4EMdw2PRN389OneM5+d844tirkGp4dPKzI5OE72V9OsbFp+CIYJDahZ41ZV05hNtcPAQUAm9/Q== + dependencies: + through2 "^2.0.3" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +touch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== + dependencies: + nopt "~1.0.10" + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +uglify-js@^3.1.4: + version "3.17.4" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c" + integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g== + +unc-path-regex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" + integrity sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg== + +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== + +unique-stream@^2.0.2, unique-stream@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.3.1.tgz#c65d110e9a4adf9a6c5948b28053d9a8d04cbeac" + integrity sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A== + dependencies: + json-stable-stringify-without-jsonify "^1.0.1" + through2-filter "^3.0.0" + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +unxhr@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unxhr/-/unxhr-1.0.1.tgz#92200322d66c728993de771f9e01eeb21f41bc7b" + integrity sha512-MAhukhVHyaLGDjyDYhy8gVjWJyhTECCdNsLwlMoGFoNJ3o79fpQhtQuzmAE4IxCMDwraF4cW8ZjpAV0m9CRQbg== + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +value-or-function@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813" + integrity sha512-jdBB2FrWvQC/pnPtIqcLsMaQgjhdb6B7tk1MMyTKapox+tQZbdRP4uLxu/JY0t7fbfDCUMnuelzEYv5GsxHhdg== + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +vinyl-fs@^3.0.3, vinyl-fs@~3.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.3.tgz#c85849405f67428feabbbd5c5dbdd64f47d31bc7" + integrity sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng== + dependencies: + fs-mkdirp-stream "^1.0.0" + glob-stream "^6.1.0" + graceful-fs "^4.0.0" + is-valid-glob "^1.0.0" + lazystream "^1.0.0" + lead "^1.0.0" + object.assign "^4.0.4" + pumpify "^1.3.5" + readable-stream "^2.3.3" + remove-bom-buffer "^3.0.0" + remove-bom-stream "^1.2.0" + resolve-options "^1.1.0" + through2 "^2.0.0" + to-through "^2.0.0" + value-or-function "^3.0.0" + vinyl "^2.0.0" + vinyl-sourcemap "^1.1.0" + +vinyl-sourcemap@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz#92a800593a38703a8cdb11d8b300ad4be63b3e16" + integrity sha512-NiibMgt6VJGJmyw7vtzhctDcfKch4e4n9TBeoWlirb7FMg9/1Ov9k+A5ZRAtywBpRPiyECvQRQllYM8dECegVA== + dependencies: + append-buffer "^1.0.2" + convert-source-map "^1.5.0" + graceful-fs "^4.1.6" + normalize-path "^2.1.1" + now-and-later "^2.0.0" + remove-bom-buffer "^3.0.0" + vinyl "^2.0.0" + +vinyl@^2.0.0, vinyl@^2.0.2, vinyl@~2.2: + version "2.2.1" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.1.tgz#23cfb8bbab5ece3803aa2c0a1eb28af7cbba1974" + integrity sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw== + dependencies: + clone "^2.1.1" + clone-buffer "^1.0.0" + clone-stats "^1.0.0" + cloneable-readable "^1.0.0" + remove-trailing-separator "^1.0.1" + replace-ext "^1.0.0" + +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +xdg-basedir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" + integrity sha512-1Dly4xqlulvPD3fZUQJLY+FUIeqN3N2MM3uqe4rCJftAvOjFa3jFGfctOgluGx4ahPbUCsZkmJILiP0Vi4T6lQ== + +xtend@~4.0.0, xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +yargs-parser@^20.2.7: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yauzl@^2.2.1: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + +yazl@^2.2.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35" + integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw== + dependencies: + buffer-crc32 "~0.2.3" From 37817c9fe62c17bfaf831df7d263146affba05fb Mon Sep 17 00:00:00 2001 From: angrykoala Date: Mon, 29 Jan 2024 17:10:16 +0100 Subject: [PATCH 10/23] WIP: draft on migration guide for 5.0 --- modules/ROOT/pages/migration/index.adoc | 1015 +++-------------------- 1 file changed, 111 insertions(+), 904 deletions(-) diff --git a/modules/ROOT/pages/migration/index.adoc b/modules/ROOT/pages/migration/index.adoc index 6e0f6015..c07d75ca 100644 --- a/modules/ROOT/pages/migration/index.adoc +++ b/modules/ROOT/pages/migration/index.adoc @@ -1,9 +1,9 @@ [[v5-migration]] -:description: This page lists the breaking changes from version 3.0.0 to 4.0.0 and describes how to update. -:page-aliases: guides/v5-migration/index.adoc , migration/v5-migration/index.adoc +:description: This page lists the breaking changes from version 4.0.0 to 5.0.0 and describes how to update. +:page-aliases: guides/index.adoc, guides/migration-guide/index.adoc, guides/migration-guide/server.adoc, guides/migration-guide/type-definitions.adoc, guides/migration-guide/mutations.adoc = Migration to 5.0.0 -This page lists all breaking changes from the Neo4j GraphQL Library version 3.x to 4.x and how to update it. +This page lists all breaking changes from the Neo4j GraphQL Library version 4.x to 5.x and how to update it. == How to update @@ -16,997 +16,204 @@ npm update @neo4j/graphql == Breaking changes -Here is a list of all the breaking changes from version 3.0.0 to 4.0.0. +Here is a list of all the breaking changes from version 4.0.0 to 5.0.0. -=== `IExecutableSchemaDefinition` +=== `@relationshipProperties` -If you were passing any arguments from https://the-guild.dev/graphql/tools/docs/api/interfaces/schema_src.iexecutableschemadefinition[`IExecutableSchemaDefinition`] into the library other than `typeDefs` and `resolvers`, these are no longer supported. - -=== `config.enableDebug` - -The programmatic toggle for debug logging has been moved from `config.enableDebug` to simply `debug`. - -[cols="1,1"] -|=== -|Before | Now - -a| -[source, javascript, indent=0] ----- -const { Neo4jGraphQL } = require("@neo4j/graphql"); -const neo4j = require("neo4j-driver"); -const { ApolloServer } = require("apollo-server"); - -const typeDefs = ` - type Movie { - title: String! - } -`; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("username", "password") -); - -const neoSchema = new Neo4jGraphQL({ - typeDefs, - driver, - config: { - enableDebug: true, - } -}); ----- -a| -[source, javascript, indent=0] ----- -const { Neo4jGraphQL } = require("@neo4j/graphql"); -const neo4j = require("neo4j-driver"); -const { ApolloServer } = require("apollo-server"); - -const typeDefs = ` - type Movie { - title: String! - } -`; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("username", "password") -); - -const neoSchema = new Neo4jGraphQL({ - typeDefs, - driver, - debug: true, -}); ----- -|=== - -=== `driverConfig` - -Session configuration is now available only in the context under the `sessionConfig` key. -Additionally, the `bookmarks` key has been removed as it is no longer needed with the bookmark manager of the newer driver. +The directive `@relationshipProperties` should now be used on types rather than interfaces. +For example: [cols="1,1"] |=== |Before | Now a| -[source, javascript, indent=0] ----- -import { ApolloServer } from '@apollo/server'; -import { startStandaloneServer } from '@apollo/server/standalone'; -import { Neo4jGraphQL } from "@neo4j/graphql"; -import neo4j from "neo4j-driver"; - -const typeDefs = `#graphql - type User { - name: String - } -`; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("username", "password") -); - -const neoSchema = new Neo4jGraphQL({ - typeDefs, - config: { - driverConfig: { - database: "different-db" - }, - }, -}) - -const server = new ApolloServer({ - schema: await neoSchema.getSchema(), -}); - -await startStandaloneServer(server, { - context: async ({ req }) => ({ req }), -}); - ----- -a| -[source, javascript, indent=0] ----- -import { ApolloServer } from '@apollo/server'; -import { startStandaloneServer } from '@apollo/server/standalone'; -import { Neo4jGraphQL } from "@neo4j/graphql"; -import neo4j from "neo4j-driver"; - -const typeDefs = `#graphql - type User { - name: String - } -`; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("username", "password") -); - -const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); - -const server = new ApolloServer({ - schema: await neoSchema.getSchema(), -}); - -await startStandaloneServer(server, { - context: async ({ req }) => ({ sessionConfig: { database: "my-database" }}), -}); ----- -|=== - -=== `config.enableRegex` - -This has been replaced by `MATCHES` in features.filters. -With this change comes more granularity in the feature configuration. -You can now enable the `MATCHES` filter on `String` and `ID` fields separately: - -[cols="1,1"] -|=== -|Before | After - -a| -[source, javascript, indent=0] ----- -neoSchema = new Neo4jGraphQL({ - typeDefs, - config: { - enableRegex: true - } -}); ----- -a| -[source, javascript, indent=0] ----- -neoSchema = new Neo4jGraphQL({ - typeDefs, - features: { - filters: { - String: { - MATCHES: true, - }, - ID: { - MATCHES: true, - }, - }, - }, -}); ----- -|=== - -=== `queryOptions` - -If you had a need to pass in Cypher query options for query tuning, this interface has been changed. -The config option `queryOptions` has now become `cypherQueryOptions` inside the context function, and it now accepts simple strings instead of enums. -This change reflects the fact that the Cypher query options are set on a per-request basis. - -[cols="1,1"] -|=== -|Before | Now - -a| -[source, javascript, indent=0] ----- -const { Neo4jGraphQL, CypherRuntime } = require("@neo4j/graphql"); -const { ApolloServer } = require("apollo-server"); - -const typeDefs = ` - type Movie { - title: String! - } -`; - -const neoSchema = new Neo4jGraphQL({ - typeDefs, - config: { - queryOptions: { - runtime: CypherRuntime.INTERPRETED, - }, - }, -}); - -const server = new ApolloServer({ - schema: await neoSchema.getSchema(), -}); - -await startStandaloneServer(server, { - context: async ({ req }) => ({ req }), -}); ----- - -a| -[source, javascript, indent=0] ----- -const { Neo4jGraphQL } = require("@neo4j/graphql"); -const { ApolloServer } = require("apollo-server"); - -const typeDefs = ` - type Movie { - title: String! - } -`; - -const neoSchema = new Neo4jGraphQL({ - typeDefs, -}); - -const server = new ApolloServer({ - schema: await neoSchema.getSchema(), -}); - -await startStandaloneServer(server, { - context: async ({ req }) => ({ cypherQueryOptions: { runtime: "interpreted" }}), -}); ----- -|=== - -=== `skipValidateTypeDefs` - -The argument has been moved to the top-level of the constructor input and renamed `validate`, which defaults to `true`. -If you started using the `config.startupValidation` option, this has also been rolled into the same `validate` setting for simplicity. - -Likewise, the `resolvers` option is now just a warning, and `noDuplicateRelationshipFields` is now a mandatory check rolled into `validate`. - -Here is an example query of how it looks now: - -[cols="1,1"] -|=== -|Before | After - -a| -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - config: { - skipValidateTypeDefs: true, - }, -}) ----- -a| -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - validate: false, -}) ----- -|=== - -=== `@cypher` - -The default behavior of the `@cypher` directive regarding the translation has changed. -Instead of using https://neo4j.com/labs/apoc/4.0/overview/apoc.cypher/apoc.cypher.runFirstColumnMany/[apoc.cypher.runFirstColumnMany], it directly wraps the query within a `CALL { }` subquery. - -This update has proven to be more performant for the same queries, however, it may lead to unexpected changes, mainly when using Neo4j 5.x, where the subqueries need to be _aliased_. - -On top of that, to improve performance, it is recommended to pass the returned alias in the property `columnName`, to ensure the subquery is properly integrated into the larger query. - -For example, the GraphQL query: - [source, graphql, indent=0] ---- -type query { - test: String! @cypher(statement: "RETURN 'hello'") +interface ActedIn @relationshipProperties { + screenTime: Int } ----- -Would be translated to: -[source,cypher, indent=0] ----- -CALL { - RETURN 'hello' +type Movie { + title: String + actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") } -WITH 'hello' AS this -RETURN this ----- -Which is invalid in Neo4j 5.x. -To fix it, ensure the `RETURN` elements are aliased: - -[source, graphql, indent=0] ----- -type query { - test: String! @cypher(statement: "RETURN 'hello' as result") +type Person { + name: String } ---- - -Another way to use this update is through an experimental option with the `columnName` flag in the `@cypher` directive: - +a| [source, graphql, indent=0] ---- -type query { - test: String! @cypher(statement: "RETURN 'hello' as result", columnName: "result") +type ActedIn @relationshipProperties { + screenTime: Int } ----- - -Note that escaping strings are no longer needed in Neo4j GraphQL 4.0.0. -=== `@fulltext` - -In version 4.0.0, a number of improvements have been made to full-text queries. -These include the ability to return the full-text score, filter by the score and sorting by the score. -However, these improvements required a number of breaking changes. - -==== Full-text queries - -Full-text queries now need to be performed using a top-level query, instead of being performed using an argument on a node query. - -As a result, the following query is now invalid: - -[source, graphql, indent=0] ----- -query { - movies(fulltext: { movieTitleIndex: { phrase: "Some Title" } }) { - title - } +type Movie { + title: String + actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") } ----- - -The new top-level queries can be used to return the full-text score, which indicates the confidence of a match, as well as the nodes that have been matched. -They now accept the following arguments: - -* `phrase`: specifies the string to search for in the full-text index. -* `where`: accepts a min/max score as well as the normal filters available on a node. -* `sort: used to sort using the score and node attributes. -* `limit`: used to limit the number of results to the given integer. -* `offset`: used to offset by the given number of results. -This means that, for the following type definition: - -[source, graphql, indent=0] ----- -type Movie @fulltext(indexes: [{ indexName: "MovieTitle", fields: ["title"] }]) { # Note that indexName is the new name for the name argument. More about this below. - title: String! +type Person { + name: String } ---- +|=== -The following top-level query and type definitions would be generated by the library: +=== Interface directives -[source, graphql, indent=0] ----- -type Query { - movieFulltextMovieTitle(phrase: String!, where: MovieFulltextWhere, sort: [MovieFulltextSort!], limit: Int, offset: Int): [MovieFulltextResult!]! -} +To better match the default behavior of GraphQL, library directives used in interfaces will not cascade to the implementing types anymore. +This means that most directives are no longer valid in interfaces and have to be defined in the implementing types. +This also applies to custom directives. -"""The result of a fulltext search on an index of Movie""" -type MovieFulltextResult { - score: Float - movies: Movie -} -"""The input for filtering a fulltext query on an index of Movie""" -input MovieFulltextWhere { - score: FloatWhere - movie: MovieWhere -} -"""The input for sorting a fulltext query on an index of Movie""" -input MovieFulltextSort { - score: SortDirection - movie: MovieSort -} +=== `@declareRelationship` -"""The input for filtering the score of a fulltext search""" -input FloatWhere { - min: Float - max: Float -} ----- +The `@relationship` directive is no longer available in interfaces. +If you need a relationship to be available on interfaces, you need to use the new `@declareRelationship` directive instead, as well as define the relationships in the concrete type. -This query can then be used to perform a full-text query: +This change is due to directives no longer cascading from interfaces to types. +However, now it is possible for a relationship to have different properties and labels in each type. +For example: +[cols="1,1"] +|=== +|Before | Now +a| [source, graphql, indent=0] ---- -query { - movieFulltextMovieTitle( - phrase: "Full Metal Jacket", - where: { score: min: 0.4 }, - sort: [{ movie: { title: ASC } }], - limit: 5, - offset: 10 - ) { - score - movies { - title - } - } +interface Production { + title: String! + actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) } ----- - -And thus return results in the following format: -[source, json, indent=0] ----- -{ - "data": { - "movieFulltextMovieTitle": [ - { - "score": 0.44524085521698, - "movie": { - "title": "Full Moon High" - } - }, - { - "score": 1.411118507385254, - "movie": { - "title": "Full Metal Jacket" - } - } - ] - } +type Movie implements Production { + title: String! + actors: [Actor!]! } ----- - -==== Argument changes - -The following changes have been made to `@fulltext` arguments: -* `queryName` has been added to specify a custom name for the top-level query that is generated. -* `name` has been renamed to `indexName` to avoid ambiguity with the new `queryName` argument. - -These changes mean that the following type definition is now invalid: +type Series implements Production { + title: String! + episodes: Int! + actors: [Actor!]! +} -[source, graphql, indent=0] ----- -type Movie @fulltext(indexes: [{ name: "MovieTitle", fields: ["title"] }]) { - title: String! +type Actor { + name: String! } ---- - -The `name` argument now needs to be replaced with `indexName`: - +a| [source, graphql, indent=0] ---- -type Movie @fulltext(indexes: [{ indexName: "MovieTitle", fields: ["title"] }]) { - title: String! +interface Production { + title: String! + actors: [Actor!]! @declareRelationship } ----- - -As an example, the `queryName` argument can be used as: -[source, graphql, indent=0] ----- -type Movie @fulltext(indexes: [{ queryName: "moviesByTitle", indexName: "MovieTitle", fields: ["title"] }]) { - title: String! +type Movie implements Production { + title: String! + actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) } ----- -This means the top-level query is now `moviesByTitle` instead of `movieFulltextMovieTitle`: +type Series implements Production { + title: String! + episodes: Int! + actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN) +} -[source, graphql, indent=0] ----- -type Query { - moviesByTitle(phrase: String!, where: MovieFulltextWhere, sort: [MovieFulltextSort!], limit: Int, offset: Int): [MovieFulltextResult!]! +type Actor { + name: String! } ---- - -== Subscription options - -Subscriptions are no longer configured as a plugin, but as a feature within the `features` option. - -[cols="1,1"] |=== -|Before | Now -a| -[source, javascript] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - plugins: { - subscriptions: plugin, - }, -}); ----- -a| -[source, javascript] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - features: { - subscriptions: plugin, - }, -}); ----- -|=== -=== Default subscriptions +=== Edge properties -The class `Neo4jGraphQLSubscriptionsSingleInstancePlugin` is no longer exported. -Instead, the default subscriptions behavior can be enabled by setting the `subscriptions` option to `true` . +Edge properties in connections now exist within the field `properties` in `edges`: [cols="1,1"] |=== |Before | Now - -a| -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - plugin: { - subscriptions: new Neo4jGraphQLSubscriptionsSingleInstancePlugin(), - }, -}); ----- -a| -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - features: { - subscriptions: true - }, -}); ----- -|=== - -=== Neo4j GraphQL subscriptions AMQP package - -The name of the interface underlying the subscriptions system has changed from `Neo4jGraphQLSubscriptionsPlugin` to `Neo4jGraphQLSubscriptionsEngine`. -If you were previously using the `@neo4j/graphql-plugins-subscriptions-amqp` package, this has been changed to `@neo4j/graphql-amqp-subscriptions-engine` to reflect this underlying change. - -To keep using it, uninstall the previous package and install the new one: - -[source, bash, indent=0] ----- -npm uninstall @neo4j/graphql-plugins-subscriptions-amqp -npm install @neo4j/graphql-amqp-subscriptions-engine ----- - -Then update any imports: - -[cols="1,1"] -|=== -|From | To - -a| -[source, javascript, indent=0] ----- -import { Neo4jGraphQLSubscriptionsAMQPPlugin } from "@neo4j/graphql-plugins-subscriptions-amqp"; ----- -a| -[source, javascript, indent=0] ----- -import { Neo4jGraphQLAMQPSubscriptionsEngine } from "@neo4j/graphql-amqp-subscriptions-engine"; ----- -|=== - -And change the instantiations: - -[cols="1,1"] -|=== -|From | To - -a| -[source, javascript, indent=0] ----- -const plugin = new Neo4jGraphQLSubscriptionsAMQPPlugin({ - connection: { - hostname: "localhost", - username: "guest", - password: "guest", - }, -}); ----- -a| -[source, javascript, indent=0] ----- -const subscriptionsEngine = new Neo4jGraphQLAMQPSubscriptionsEngine({ - connection: { - hostname: "localhost", - username: "guest", - password: "guest", - }, -}); ----- -|=== - -=== Custom subscription plugins - -The underlying subscription system has not changed. -Custom behavior can be implemented the same way, by creating a class implementing the interface described in xref::subscriptions/engines.adoc#custom-subscription[Subscriptions engines]. - -However, if using TypeScript, the exported interface to implement these classes has been renamed from `Neo4jGraphQLSubscriptionsPlugin` to `Neo4jGraphQLSubscriptionsEngine`. - -== Updated directives - -A number of directives and their arguments have been renamed in order to make using `@neo4j/graphql` more intuitive. -Here is a table with all the changes: - -[cols="1,2,2"] -|=== -|Before | Now | Example - -|`@alias` -|Properties in the alias directive are now automatically escaped using backticks. -If you were using backticks in the `property` argument of your `@alias` directives, you should now remove the escape strings as this is covered by the library. a| [source, graphql, indent=0] ---- -type User { - id: ID! @id - username: String! @alias(property: "dbUserName") -} ----- - -|`@callback` -|Renamed to `@populatedBy`. -Additionally, the `name` argument has been renamed to `callback` and it is still used to specify the callback used to populate the field's value. -a| -.Before -[source, graphql, indent=0] ----- -type User { - id: ID! @callback(name: "nanoid", operations: [CREATE]) - firstName: String! - surname: String! -} ----- - -.Now -[source, graphql, indent=0] ----- -new Neo4jGraphQL({ - typeDefs, - features: { // changed from config - populatedBy: { // changed from callback - callbacks: { - nanoid: () => { return nanoid(); } +query ActedInConnection { + actors { + actedInConnection { + edges { + screenTime } } } -}); ----- - -|`@computed` -a|Renamed to `@customResolver`. -Note that before and after these changes, a custom resolver needs to be defined as follows: - -[source, javascript, indent=0] ----- -new Neo4jGraphQL({ - typeDefs, - resolvers: { - User: { - fullName: ({ firstName, lastName }, args, context, info) => (`${firstName} ${lastName}`), - } - } -}); ----- - -a| -.Before -[source, graphql, indent=0] ----- -type User { - firstName: String! - lastName: String! - fullName: String! @computed(from: ["firstName", "lastName"]) -} ----- - -.Now -[source, graphql, indent=0] ----- -type User { - firstName: String! - lastName: String! - fullName: String! @customResolver(requires: ["firstName", "lastName"]) -} ----- - -|`from` -a| Renamed to `requires`. -In version 4.0.0, it is now possible to require non-scalar fields, which means it is also possible to require fields on related type. + -{nbsp} + -Additionally, the `requires` argument now accepts a GraphQL selection set instead of a list of strings and also validates the required selection set against your type definitions. -This means that if there is no field called `someFieldThatDoesNotExist`, an error would be thrown on startup if you tried to use the following type definitions: + -{nbsp} + -[source, graphql, indent=0] ----- -type User { - firstName: String! - lastName: String! - fullName: String! @customResolver(requires: "firstName someFieldThatDoesNotExist") -} ----- - -a| -.Before -[source, graphql, indent=0] ----- -type User { - firstName: String! - lastName: String! - fullName: String! @customResolver(requires: ["firstName", "lastName"]) -} ----- - -.Now -[source, graphql, indent=0] ----- -type User { - firstName: String! - lastName: String! - fullName: String! @customResolver(requires: "firstName lastName") -} ----- - -.Additional example -[source, graphql, indent=0] ----- -interface Publication { - publicationYear: Int! -} - -type Author { - name: String! - publications: [Publication!]! @relationship(type: "WROTE", direction: OUT) - publicationsWithAuthor: [String!]! - @customResolver( - requires: "name publications { publicationYear ...on Book { title } ... on Journal { subject } }" - ) -} - -type Book implements Publication { - title: String! - publicationYear: Int! - author: [Author!]! @relationship(type: "WROTE", direction: IN) -} - -type Journal implements Publication { - subject: String! - publicationYear: Int! - author: [Author!]! @relationship(type: "WROTE", direction: IN) } ---- - -|`@exclude` -|Replaced by `@query`, `@mutation`, and `@subscription`. -These new directives allow for fully granular configuration for each operation. -a|* `@exclude` -> `@query(read: false, aggregate: false) @mutation(operations: []) @subscription(events: [])`. + -* `@exclude(operations: [READ])` -> `@query(read: false, aggregate: false)`. + -* `@exclude(operation: [CREATE, UPDATE, DELETE])` -> `@mutation(operations: [])`. - -|`@id` -a|Deprecated with _all_ of its arguments removed and/or replaced. -a|*`autogenerate` -> `@unique`* + -The default value was `true`. -If set to `false`, the `@id` directive was almost a no-op only used to manage a unique node property constraint. -Use the `@unique` directive instead. + -{nbsp} + -*`global` -> `@relayId`* + -This argument was used to configure the field that would form the global node identifier for Relay. -This functionality has been moved into its own directive, `@relayId`. The use of `@relayId` will ensure a unique node property constraint for the field. + -{nbsp} + -*`@id` -> `unique` + `@id`* + -The `@id` directive used to also manage unique node property constraints for a field. -This functionality has been removed. -Use the `@unique` directive in combination with `@id` if you want the field to be backed by a constraint. - -|`@plural` -|Removed from `@node` and replaced by the `@plural` directive. -It takes the pluralized type name using the `value` argument. a| -.Invalid `plural` type definition [source, graphql, indent=0] ---- -type Tech @node(label: "TechDB", plural: "Techs") { - name: String +query ActedInConnection { + actors { + actedInConnection { + edges { + properties { + screenTime + } + } + } + } } ---- +|=== -.Updated version -[source, graphql, indent=0] ----- -type Tech @node(label: "TechDB") @plural(value: "Techs") { - name: String -} ----- +=== `_on` filters deprecated -|`label` and `additionalLabels` -a|Removed from `@node` and replaced by `labels`. -It accepts a list of string labels that are used when a node of the given GraphQL type is created. + -{nbsp} + -Note that defining `labels` means taking control of the database labels of the node. -Indexes and constraints in Neo4j only support a single label, for which the first element of the `labels` argument will be used. + -{nbsp} + -As before, providing none of these arguments results in the node label being the same as the GraphQL type name. -This can cause implications on constraints. -For instance, in the case where unique constraint is asserted for the label `Tech` and the property `name`: + -{nbsp} + -[source, graphql, indent=0] ----- -type Tech @node(labels: ["Tech", "TechDB"]) { - name: String @unique -} ----- -a| -.Current equivalent to `label` -[source, graphql, indent=0] ----- -type Tech @node(label: "TechDB") { - name: String -} -# becomes -type Tech @node(labels: ["TechDB"]) { - name: String -} ----- +`_on` filters for interfaces are no longer available in `where` and mutations. To filter by an implementing type, you need to use the new filter `typename_IN`: -.Current equivalent to `additionalLabels` -[source, graphql, indent=0] ----- -type Tech @node(additionalLabels: ["TechDB"]) { - name: String -} -# becomes -type Tech @node(labels: ["Tech", "TechDB"]) { - name: String -} ----- -.Current equivalent to both arguments -[source, graphql, indent=0] ----- -type Tech @node(label: "TechDB", additionalLabels: ["AwesomeTech"]) { - name: String -} -# becomes -type Tech @node(labels: ["TechDB", "AwesomeTech"]) { - name: String -} ----- - -|`@queryOptions` and `limit` -| Removed and moved to `@limit`. +[cols="1,1"] +|=== +|Before | Now a| -.Outdated example [source, graphql, indent=0] ---- -type Record @queryOptions(limit: { default: 10, max: 100 }) { - id: ID! -} ----- - -.Updated version using `@limit` -[source, graphql, indent=0] ----- -type Record @limit(default: 10, max: 100) { - id: ID! +query MyQuery { + actors( + where: { + actedInConnection_SINGLE: { node: { _on: { Movie: { } } } } + } + ) { + name + } } ---- - -|`@readonly` and `@writeonly` -|Removed and replaced by the `@selectable` and `@settable` directives. -They can be used to configure not only if fields are readable or writable, but also when they should be readable or writable. -a|* `@readonly` -> `@settable(onCreate: false, onUpdate: false)` -* `@writeonly` -> `@selectable(onRead: false, onAggregate: false)` - -|`@query` and `@relationship` -|Aggregation operations are no longer generated by default. -They can be enabled case by case using the directives xref::/schema-configuration/type-configuration.adoc#_query[`@query`] and xref::/schema-configuration/field-configuration.adoc#_relationship[`@relationship`]. a| [source, graphql, indent=0] ---- -type Movie { - title: String! -} - -type Actor @query(aggregate: true) { - name: String! - actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, aggregate: true) +query MyQuery { + actors( + where: { + actedInConnection_SINGLE: { node: { typename_IN: [Movie] } } + } + ) { + name + } } ---- |=== -[relationship-aggregate] -== Relationship updates - -Here are the changes and updates to `@relationship`-related features. - -[discrete] -=== *Relationship types are now automatically escaped* - -Relationship types are now automatically escaped. -If you have previously escaped your relationship types using backticks, you must now remove these as this is covered by the library. - -[discrete] -=== *`@relationshipProperties` now mandatory* - -Current changes require the distinction between interfaces that are used to specify relationship properties, and others. -Therefore, the `@relationshipProperties` directive is now required on all relationship property interfaces. -If it is not included, an error is thrown. - -As a result, in version 4.0.0, the following type definitions are invalid: - -[source, graphql, indent=0] ----- -type Person { - name: String! - movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedIn") -} - -type Movie { - title: String! - actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") -} - -interface ActedIn { - screenTime: Int! -} ----- - -`ActedIn` must be decorated with `@relationshipProperties`: - -[source, graphql, indent=0] ----- -interface ActedIn @relationshipProperties { - screenTime: Int! -} ----- - -[discrete] -=== Duplicate relationship fields are now checked for -In 3.0.0, it was possible to define schemas with types that have multiple relationship fields connected by the same type of relationships. -Now, this kind of scenario is detected during schema generation and an error is thrown so developers are informed to fix the type definitions. +[NOTE] +==== +Using fields of a type in an interface operation is no longer supported. +==== -Here is an example of what is now considered invalid with these checks: +==== Minimum NodeJS version -[source, graphql, indent=0] ----- -type Team { - player1: Person! @relationship(type: "PLAYS_IN", direction: IN) - player2: Person! @relationship(type: "PLAYS_IN", direction: IN) - backupPlayers: [Person!]! @relationship(type: "PLAYS_IN", direction: IN) -} - -type Person { - teams: [Team!]! @relationship(type: "PLAYS_IN", direction: OUT) -} ----- - -In this example, there are multiple fields in the `Team` type which have the same `Person` type, the same `@relationship` type and ("PLAYS_IN") direction (IN). This is an issue when returning data from the database, as there would be no difference between `player1`, `player2` and `backupPlayers`. Selecting these fields would then return the same data. - -These checks can be disabled by disabling all validation in the library, however, this is not recommended unless in production with 100% confidence of type definitions input. - -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - validate: false, -}); ----- +With the deprecation of Node 16, the minimum supported NodeJS version is `18.0.0`. -== `cypherParams` +==== `experimental` flag -In 3.0.0, `cypherParams` was available in the context to provide the ability to pass arbitrary parameters to a custom Cypher query. -This functionality remains in 4.0.0, but you no longer have to use the `$cypherParams` prefix to reference these parameters. +The `experimental` flag is no longer available in the options of the `Neo4jGraphQL` class. \ No newline at end of file From ad608af13e43ee269c1a91097966944775c3b90f Mon Sep 17 00:00:00 2001 From: angrykoala Date: Tue, 30 Jan 2024 10:28:55 +0100 Subject: [PATCH 11/23] Migration guide to 5.0.0 --- modules/ROOT/pages/migration/index.adoc | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/modules/ROOT/pages/migration/index.adoc b/modules/ROOT/pages/migration/index.adoc index c07d75ca..2313587b 100644 --- a/modules/ROOT/pages/migration/index.adoc +++ b/modules/ROOT/pages/migration/index.adoc @@ -20,8 +20,12 @@ Here is a list of all the breaking changes from version 4.0.0 to 5.0.0. === `@relationshipProperties` +<<<<<<< HEAD The directive `@relationshipProperties` should now be used on types rather than interfaces. For example: +======= +The directive `@relationshipProperties` should now be used on types rather than interfaces: +>>>>>>> 20f6bef (Migration guide to 5.0.0) [cols="1,1"] |=== @@ -74,9 +78,13 @@ This also applies to custom directives. The `@relationship` directive is no longer available in interfaces. If you need a relationship to be available on interfaces, you need to use the new `@declareRelationship` directive instead, as well as define the relationships in the concrete type. +<<<<<<< HEAD This change is due to directives no longer cascading from interfaces to types. However, now it is possible for a relationship to have different properties and labels in each type. For example: +======= +This is due to directives no longer cascading from interfaces to types and allows for a relationship to have different properties and labels in each type. +>>>>>>> 20f6bef (Migration guide to 5.0.0) [cols="1,1"] |=== @@ -170,7 +178,11 @@ query ActedInConnection { === `_on` filters deprecated +<<<<<<< HEAD `_on` filters for interfaces are no longer available in `where` and mutations. To filter by an implementing type, you need to use the new filter `typename_IN`: +======= +`_on` filters for interfaces are no longer available in `where` and mutations. To filter by a implementing type you need to use the new filter `typename_IN`: +>>>>>>> 20f6bef (Migration guide to 5.0.0) [cols="1,1"] @@ -210,10 +222,10 @@ query MyQuery { Using fields of a type in an interface operation is no longer supported. ==== -==== Minimum NodeJS version +=== Minimum Node updated to `18.0.0` -With the deprecation of Node 16, the minimum supported NodeJS version is `18.0.0`. +With the deprecation of Node 16, the minimum supported NodeJS version is `18.0.0` -==== `experimental` flag +=== `experimental` flag removed -The `experimental` flag is no longer available in the options of the `Neo4jGraphQL` class. \ No newline at end of file +The `experimental` flag is no longer available on the options of `Neo4jGraphQL` class. From ad73d6689e54e14f29ba2318c6ced1f76cb05b42 Mon Sep 17 00:00:00 2001 From: angrykoala Date: Tue, 30 Jan 2024 16:32:22 +0100 Subject: [PATCH 12/23] Update examples in docs to comply with 5.0 --- .../pages/queries-aggregations/filtering.adoc | 2 +- .../pages/queries-aggregations/index.adoc | 4 +- modules/ROOT/pages/subscriptions/events.adoc | 26 +- .../ROOT/pages/subscriptions/filtering.adoc | 108 +------ modules/ROOT/pages/troubleshooting.adoc | 50 +--- .../type-definitions/directives/basics.adoc | 7 +- .../directives/database-mapping.adoc | 2 +- .../type-definitions/types/interfaces.adoc | 268 +----------------- .../type-definitions/types/relationships.adoc | 6 +- 9 files changed, 34 insertions(+), 439 deletions(-) diff --git a/modules/ROOT/pages/queries-aggregations/filtering.adoc b/modules/ROOT/pages/queries-aggregations/filtering.adoc index 4bce12eb..eb77fbf3 100644 --- a/modules/ROOT/pages/queries-aggregations/filtering.adoc +++ b/modules/ROOT/pages/queries-aggregations/filtering.adoc @@ -387,7 +387,7 @@ type Person { name: String } -interface ActedIn @relationshipProperties { +type ActedIn @relationshipProperties { screenTime: Int } ---- diff --git a/modules/ROOT/pages/queries-aggregations/index.adoc b/modules/ROOT/pages/queries-aggregations/index.adoc index 328550f2..45846f8a 100644 --- a/modules/ROOT/pages/queries-aggregations/index.adoc +++ b/modules/ROOT/pages/queries-aggregations/index.adoc @@ -33,7 +33,7 @@ type User { friends: [User!]! @relationship(type: "FRIENDS_WITH", direction: OUT) } -interface PostedAt @relationshipProperties { +type PostedAt @relationshipProperties { date: DateTime } ---- @@ -49,4 +49,4 @@ type Query { users(where: UserWhere, options: UserOptions): [User!]! usersAggregate(where: UserWhere): UserAggregationSelection! } ----- \ No newline at end of file +---- diff --git a/modules/ROOT/pages/subscriptions/events.adoc b/modules/ROOT/pages/subscriptions/events.adoc index 7a003050..ac48f133 100644 --- a/modules/ROOT/pages/subscriptions/events.adoc +++ b/modules/ROOT/pages/subscriptions/events.adoc @@ -179,7 +179,7 @@ type Actor { name: String } -interface ActedIn @relationshipProperties { +type ActedIn @relationshipProperties { screenTime: Int! } @@ -255,7 +255,7 @@ type Actor { name: String } -interface ActedIn @relationshipProperties { +type ActedIn @relationshipProperties { screenTime: Int! } ---- @@ -312,7 +312,7 @@ type Person { reputation: Int } -interface Directed @relationshipProperties { +type Directed @relationshipProperties { year: Int! } ---- @@ -374,7 +374,7 @@ type Influencer implements Reviewer { reputation: Int! } -interface Review @relationshipProperties { +type Review @relationshipProperties { score: Int! } ---- @@ -439,11 +439,11 @@ type Person { union Director = Person | Actor -interface ActedIn @relationshipProperties { +type ActedIn @relationshipProperties { screenTime: Int! } -interface Directed @relationshipProperties { +type Directed @relationshipProperties { year: Int! } ---- @@ -1010,7 +1010,7 @@ type Actor {s name: String } -interface ActedIn @relationshipProperties { +type ActedIn @relationshipProperties { screenTime: Int! } @@ -1019,7 +1019,7 @@ type Reviewer { reputation: Int } -interface Reviewed @relationshipProperties { +type Reviewed @relationshipProperties { score: Int! } ---- @@ -1086,7 +1086,7 @@ type Actor { name: String } -interface ActedIn @relationshipProperties { +type ActedIn @relationshipProperties { screenTime: Int! } ---- @@ -1144,7 +1144,7 @@ type Person { reputation: Int } -interface Directed @relationshipProperties { +type Directed @relationshipProperties { year: Int! } ---- @@ -1205,7 +1205,7 @@ type Influencer implements Reviewer { reputation: Int! } -interface Review @relationshipProperties { +type Review @relationshipProperties { score: Int! } ---- @@ -1269,11 +1269,11 @@ type Person { union Director = Person | Actor -interface ActedIn @relationshipProperties { +type ActedIn @relationshipProperties { screenTime: Int! } -interface Directed @relationshipProperties { +type Directed @relationshipProperties { year: Int! } ---- diff --git a/modules/ROOT/pages/subscriptions/filtering.adoc b/modules/ROOT/pages/subscriptions/filtering.adoc index 63a4d160..fe542a92 100644 --- a/modules/ROOT/pages/subscriptions/filtering.adoc +++ b/modules/ROOT/pages/subscriptions/filtering.adoc @@ -238,7 +238,7 @@ type Movie { actors: [Actor!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN) } -interface ActedIn @relationshipProperties { +type ActedIn @relationshipProperties { screenTime: Int! } @@ -349,7 +349,7 @@ type Movie { reviewers: [Reviewer!]! @relationship(type: "REVIEWED", properties: "Review", direction: IN) } -interface ActedIn @relationshipProperties { +type ActedIn @relationshipProperties { screenTime: Int! } @@ -364,7 +364,7 @@ type Person implements Reviewer { union Director = Person | Actor -interface Directed @relationshipProperties { +type Directed @relationshipProperties { year: Int! } @@ -377,7 +377,7 @@ type Magazine implements Reviewer { reputation: Int! } -interface Review @relationshipProperties { +type Review @relationshipProperties { score: Int! } ---- @@ -744,60 +744,6 @@ The following example illustrates how to filter the node at the other end of the node: { # common fields declared by the interface reputation_GTE: 8 - _on: { # specific fields depending on the concrete type - Person: { # concrete type that makes up the interface type - name: "Jane Doe", - reputation_GTE: 7 - }, - Magazine: { # concrete type that makes up the interface type - title_IN: ["Sight and Sound", "Total Film"], - reputation_LT: 9 - } - } - } - }, - } - } -} ----- - -This example returns events for relationships between the type `Movie` and `Reviewer`, where the score is higher than 7, and the `Reviewer` is a `Person` named "Jane Doe", with a reputation greater or equal to 7, or the `Reviewer` is a `Magazine` with the reputation of 8. - -[NOTE] -==== -Note how the reputation field is part of the interface type, and can thus be specified in 3 ways: inside the `node` key, inside each concrete type, or in both places. + -When specified in both places, however, the filter is composed with a logical `AND`. -Type `Person` then overrides the `reputation_GTE` operator, so the final filter is `reputation_GTE: 7`. -Likewise, type `Magazine` composes the original operator so the final filter is the interval `reputation_GTE: 8 && reputation_LT: 9`. -==== - -To get all relationships of type `REVIEWED` with a certain score returned, you can use implicit filtering, such as: - -[source, graphql, indent=0] ----- -{ - where: { - deletedRelationship: { - reviewers: { - edge: { # include some relationships of type `REVIEWED` to both `Person` and `Magazine` Concrete types, that conform to the filters - score: 10 - }, - }, - } - } -} ----- - -Implicit filtering can still be used, even for relationships of type `REVIEWED` to a `Reviewer` of a specific reputation: - -[source, graphql, indent=0] ----- -{ - where: { - deletedRelationship: { - reviewers: { - node: { # include some relationships of type `REVIEWED` to both `Person` and `Magazine` Concrete types, that conform to the filters - reputation: 9 } }, } @@ -805,48 +751,4 @@ Implicit filtering can still be used, even for relationships of type `REVIEWED` } ---- -It is only when a specific concrete type needs to be filtered that you need to be explicit in the concrete types that you are interested in: - -[source, graphql, indent=0] ----- -{ - where: { - deletedRelationship: { - reviewers: { - node: { - _on: { - Person: { # include some relationships of type `REVIEWED` to Concrete type `Person`, that conform to the filters - name: "Jane Doe", - reputation_GTE: 9 - }, - } - } - }, - } - } -} ----- - -This example does not include relationships of type `REVIEWED` to the `Magazine` type. -You can do that by making them explicit: - -[source, graphql, indent=0] ----- -{ - where: { - deletedRelationship: { - reviewers: { - node: { - _on: { - Person: { # include some relationships of type `REVIEWED` to Concrete type `Person`, that conform to the filters - name: "Jane Doe", - reputation_GTE: 9 - }, - Magazine: {} # include all relationships of type `REVIEWED` to Concrete type `Magazine` - } - } - }, - } - } -} ----- +This example returns events for relationships between the type `Movie` and `Reviewer`, where the score is higher than 7, and the `Reviewer` has a reputation greater or equal to 7 diff --git a/modules/ROOT/pages/troubleshooting.adoc b/modules/ROOT/pages/troubleshooting.adoc index 4846fa62..95e268cf 100644 --- a/modules/ROOT/pages/troubleshooting.adoc +++ b/modules/ROOT/pages/troubleshooting.adoc @@ -1,6 +1,6 @@ [[troubleshooting]] = Troubleshooting -:page-aliases: troubleshooting/faqs.adoc, troubleshooting/security.adoc, troubleshooting/optimizing-create-operations.adoc, appendix/preventing-overfetching.adoc, appendix.adoc +:page-aliases: troubleshooting/faqs.adoc, troubleshooting/security.adoc, troubleshooting/optimizing-create-operations.adoc, appendix.adoc This chapter contains common troubleshooting steps. Additionally, there is a section for xref::troubleshooting.adoc#troubleshooting-faqs[FAQs] (Frequently Asked Questions) where you might find answers to your problems. @@ -218,51 +218,3 @@ This section describes security considerations and known issues. If a query yields no results, the xref::authentication-and-authorization/authorization.adoc[Authorization] process will not be triggered. This means that the result will be empty, instead of throwing an authentication error. Unauthorized users may then discern whether or not a certain type exists in the database, even if data itself cannot be accessed. - -[[appendix-preventing-overfetching]] -== Preventing overfetching - -When querying for unions and interfaces in Cypher, each union member/interface implementation is broken out into a subquery and joined with `UNION`. For example, using one of the examples above, when we query with no `where` argument, each subquery has a similar structure: - -[source, cypher, indent=0] ----- -CALL { - WITH this - OPTIONAL MATCH (this)-[has_content:HAS_CONTENT]->(blog:Blog) - RETURN { __resolveType: "Blog", title: blog.title } -UNION - WITH this - OPTIONAL MATCH (this)-[has_content:HAS_CONTENT]->(journal:Post) - RETURN { __resolveType: "Post" } -} ----- - -Now if you were to leave both subqueries and add a `WHERE` clause for blogs, it would look like this: - -[source, cypher, indent=0] ----- -CALL { - WITH this - OPTIONAL MATCH (this)-[has_content:HAS_CONTENT]->(blog:Blog) - WHERE blog.title IS NOT NULL - RETURN { __resolveType: "Blog", title: blog.title } -UNION - WITH this - OPTIONAL MATCH (this)-[has_content:HAS_CONTENT]->(journal:Post) - RETURN { __resolveType: "Post" } -} ----- - -As you can see, the subqueries are now "unbalanced", which could result in massive overfetching of `Post` nodes. - -So, when a `where` argument is passed in, only union members which are in the `where` object are fetched, so it is essentially acting as a logical OR gate, different from the rest of the `where` arguments in the schema: - -[source, cypher, indent=0] ----- -CALL { - WITH this - OPTIONAL MATCH (this)-[has_content:HAS_CONTENT]->(blog:Blog) - WHERE blog.title IS NOT NULL - RETURN { __resolveType: "Blog", title: blog.title } -} ----- diff --git a/modules/ROOT/pages/type-definitions/directives/basics.adoc b/modules/ROOT/pages/type-definitions/directives/basics.adoc index f6de6be7..7b21a3f7 100644 --- a/modules/ROOT/pages/type-definitions/directives/basics.adoc +++ b/modules/ROOT/pages/type-definitions/directives/basics.adoc @@ -43,8 +43,7 @@ Note that, in this case, there is a directive on each "end" of the relationship, === Relationship properties -In order to add properties to a relationship, you need to add a new type to your type definitions. -This time, however, it should be an interface which must be decorated with the `@relationshipProperties` directive. +In order to add properties to a relationship, you need to add a new type to your type definitions decorated with the `@relationshipProperties` directive. For example, for the "ACTED_IN" relationship, add a property "roles": @@ -60,10 +59,10 @@ type Actor { movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedIn") } -interface ActedIn @relationshipProperties { +type ActedIn @relationshipProperties { roles: [String] } ---- -Note that in addition to this interface type, there is an added a key `properties` in the existing `@relationship` directives. +Note that in addition to this type, there is an added a key `properties` in the existing `@relationship` directives. For more information, see xref::/type-definitions/types/relationships.adoc[Type definitions -> Relationships]. diff --git a/modules/ROOT/pages/type-definitions/directives/database-mapping.adoc b/modules/ROOT/pages/type-definitions/directives/database-mapping.adoc index c35fdb1a..456e2df9 100644 --- a/modules/ROOT/pages/type-definitions/directives/database-mapping.adoc +++ b/modules/ROOT/pages/type-definitions/directives/database-mapping.adoc @@ -34,7 +34,7 @@ type City { name: String } -interface UserLivesInProperties @relationshipProperties { +type UserLivesInProperties @relationshipProperties { since: DateTime @alias(property: "moveInDate") } ---- diff --git a/modules/ROOT/pages/type-definitions/types/interfaces.adoc b/modules/ROOT/pages/type-definitions/types/interfaces.adoc index 319bb4ca..ec201a3f 100644 --- a/modules/ROOT/pages/type-definitions/types/interfaces.adoc +++ b/modules/ROOT/pages/type-definitions/types/interfaces.adoc @@ -8,7 +8,7 @@ This page describes how to use and define interfaces on relationship fields. == Creating an interface field -The following schema defines a `Actor` type, that has a relationship `ACTED_IN`, of type `[Production!]!`. `Production` is an interface type with `Movie` and `Series` implementations. Note in this example that relationship properties have also been used with the `@relationshipProperties` directive, so that interfaces representing relationship properties can be easily distinguished. +The following schema defines a `Actor` type, that has a relationship `ACTED_IN`, of type `[Production!]!`. `Production` is an interface type with `Movie` and `Series` implementations. [source, graphql, indent=0] ---- @@ -29,7 +29,7 @@ type Series implements Production { episodes: Int! } -interface ActedIn @relationshipProperties { +type ActedIn @relationshipProperties { role: String! } @@ -41,45 +41,10 @@ type Actor { These type definitions will be used for the rest of the examples in this chapter. -=== Directive inheritance - -Any directives present on an interface or its fields will be "inherited" by any object types implementing it. For example, the type definitions above could be refactored to have the `@relationship` directive on the `actors` field in the `Production` interface instead of on each implementing type as it is currently: - -[source, graphql, indent=0] ----- -interface Production { - title: String! - actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") -} - -type Movie implements Production { - title: String! - actors: [Actor!]! - runtime: Int! -} - -type Series implements Production { - title: String! - actors: [Actor!]! - episodes: Int! -} - -interface ActedIn @relationshipProperties { - role: String! -} - -type Actor { - name: String! - actedIn: [Production!]! @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedIn") -} ----- - [[type-definitions-interfaced-types-querying]] == Querying an interface -Which implementations are returned by a query are dictated by the `where` filter applied. - -For example, the following will return all productions with title starting "The " for every actor: +The following will return all productions with title starting "The " for every actor: [source, graphql, indent=0] ---- @@ -99,40 +64,18 @@ query GetProductionsStartingWithThe { } ---- -Whilst the query below will only return the movies with title starting with "The " for each actor. +The query below will only return the movies with title starting with "The " for each actor by filtering by `typename_IN`. [source, graphql, indent=0] ---- query GetMoviesStartingWithThe { actors { name - actedIn(where: { node: { _on: { Movie: { title_STARTS_WITH: "The " } } } }) { - title - ... on Movie { - runtime - } - } - } -} ----- - -This is to prevent overfetching, and you can find an explanation of this xref::troubleshooting.adoc#appendix-preventing-overfetching[here]. - -Alternatively, these implementation specific filters can be used to override filtering for a specific implementation. For example, if you wanted all productions with title starting with "The ", but movies with title starting with "A ", you could achieve this using the following: - -[source, graphql, indent=0] ----- -query GetProductionsStartingWith { - actors { - name - actedIn(where: { node: { title_STARTS_WITH: "The ", _on: { Movie: { title_STARTS_WITH: "A " } } } }) { + actedIn(where: { node: { title_STARTS_WITH: "The ", typename_IN: [Movie] } }) { title ... on Movie { runtime } - ... on Series { - episodes - } } } } @@ -232,165 +175,6 @@ The above mutation: . Connects the "Woody Harrelson" node to a `Production` node with the title "Zombieland". . Connects the connected `Production` node to any `Actor` nodes with the name "Emma Stone". -This query, however, is fully abstract. -If you want to only connect `Movie` nodes to `Actor` nodes with name "Emma Stone", you could instead do: - -[source, graphql, indent=0] ----- -mutation CreateActorAndProductions { - updateActors( - where: { name: "Woody Harrelson" } - connect: { - actedIn: { - where: { node: { title: "Zombieland" } } - connect: { _on: { Movie: { actors: { where: { node: { name: "Emma Stone" } } } } } } - } - } - ) { - actors { - name - actedIn { - title - } - } - } -} ----- - -Alternatively, you can also make sure you only connect to `Movie` nodes with title "Zombieland": - -[source, graphql, indent=0] ----- -mutation CreateActorAndProductions { - updateActors( - where: { name: "Woody Harrelson" } - connect: { - actedIn: { - where: { node: { _on: { Movie: { title: "Zombieland" } } } } - connect: { actors: { where: { node: { name: "Emma Stone" } } } } - } - } - ) { - actors { - name - actedIn { - title - } - } - } -} ----- - -== Directive inheritance - -For the next example, consider the following schema. -It defines an `Actor` type, that has a relationship `ACTED_IN`, of type `[Production!]!`. -`Production` is an interface type with `Movie` and `Series` implementations. -In this example, relationship properties have also been used with the `@relationshipProperties` directive, so that interfaces representing relationship properties can be easily distinguished: - -[source, graphql, indent=0] ----- -interface Production { - title: String! - actors: [Actor!]! -} - -type Movie implements Production { - title: String! - actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") - runtime: Int! -} - -type Series implements Production { - title: String! - actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") - episodes: Int! -} - -interface ActedIn @relationshipProperties { - role: String! -} - -type Actor { - name: String! - actedIn: [Production!]! @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedIn") -} ----- - -Now, considering that any xref::/type-definitions/directives/index.adoc[directives] present on an interface or its fields are "inherited" by any object types implementing it, the example schema could be refactored to have the `@relationship` directive on the `actors` field in the `Production` interface instead of on each implementing type as it is currently. -That is how it would look like: - -[source, graphql, indent=0] ----- -interface Production { - title: String! - actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") -} - -type Movie implements Production { - title: String! - actors: [Actor!]! - runtime: Int! -} - -type Series implements Production { - title: String! - actors: [Actor!]! - episodes: Int! -} - -interface ActedIn @relationshipProperties { - role: String! -} - -type Actor { - name: String! - actedIn: [Production!]! @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedIn") -} ----- - -=== Overriding - -In addition to inheritance, directives can be overridden on a per-implementation basis. -Say you had an interface defining some `Content`, with some basic authorization rules, such as: - -[source, graphql, indent=0] ----- -interface Content - @auth(rules: [{ operations: [CREATE, UPDATE, DELETE], allow: { author: { username: "$jwt.sub" } } }]) { - title: String! - author: [Author!]! @relationship(type: "HAS_CONTENT", direction: IN) -} - -type User { - username: String! - content: [Content!]! @relationship(type: "HAS_CONTENT", direction: OUT) -} ----- - -You might implement this once for public content and once for private content which has additional rules in place: - -[source, graphql, indent=0] ----- -type PublicContent implements Content { - title: String! - author: [Author!]! -} - -type PrivateContent implements Content - @auth(rules: [{ operations: [CREATE, READ, UPDATE, DELETE], allow: { author: { username: "$jwt.sub" } } }]) { - title: String! - author: [Author!]! -} ----- - -The `PublicContent` type inherits the auth rules from the `Content` interface, while the `PrivateContent` type uses the auth rules specified there. - -In summary, there are three choices for the application of directives when using interfaces: - -* Directives specified on the interface and inherited by all implementing types when the directives for every type are the same. -* Directives specified on the interface and overridden by certain implementing types when directives are broadly the same with a few discrepancies. -* Directives specified on implementing types alone when there is very little commonality between types, or certain types need a directive and others don't. == Querying an interface @@ -414,45 +198,3 @@ query GetProductionsStartingWithThe { } } ---- - -This query, on the other hand, only returns the movies with title starting with "The" for each actor: - -[source, graphql, indent=0] ----- -query GetMoviesStartingWithThe { - actors { - name - actedIn(where: { node: { _on: { Movie: { title_STARTS_WITH: "The " } } } }) { - title - ... on Movie { - runtime - } - } - } -} ----- - -This approach aims to prevent overfetching. -For more information, read the page xref::troubleshooting.adoc#appendix-preventing-overfetching[Troubleshooting -> Preventing overfetching]. - -Alternatively, these specific filters can also be used to override filtering for a specific implementation. -For example, if you want to fetch all `series` with title starting with "The " and `movies` with title starting with "A ", you can do it like that: - -[source, graphql, indent=0] ----- -query GetProductionsStartingWith { - actors { - name - actedIn(where: { node: { title_STARTS_WITH: "The ", _on: { Movie: { title_STARTS_WITH: "A " } } } }) { - title - ... on Movie { - runtime - } - ... on Series { - episodes - } - } - } -} ----- - diff --git a/modules/ROOT/pages/type-definitions/types/relationships.adoc b/modules/ROOT/pages/type-definitions/types/relationships.adoc index b4f66812..a3d3e239 100644 --- a/modules/ROOT/pages/type-definitions/types/relationships.adoc +++ b/modules/ROOT/pages/type-definitions/types/relationships.adoc @@ -62,7 +62,7 @@ This means that a `Movie` *must* have a `DIRECTED` relationship coming into it t You can add relationship properties to the example in two steps: -. Add an interface definition decorated with the `@relationshipProperties` directive and containing the desired relationship properties. +. Add a type definition decorated with the `@relationshipProperties` directive and containing the desired relationship properties. . Add a `properties` argument to both "sides" (or just one side, if you prefer) of the `@relationship` directive which points to the newly defined interface. For example, suppose you want to distinguish which roles an actor played in a movie: @@ -83,7 +83,7 @@ type Movie { director: Person! @relationship(type: "DIRECTED", direction: IN) } -interface ActedIn @relationshipProperties { +type ActedIn @relationshipProperties { roles: [String!] } ---- @@ -291,4 +291,4 @@ type Post { ---- The relationship at `User.posts` is considered a "many" relationship, which means it should always be of type `NonNullListType` and `NonNullNamedType`. -In other words, both the array and the type inside of a "many" relationship should have a `!`. \ No newline at end of file +In other words, both the array and the type inside of a "many" relationship should have a `!`. From 354a050526573be380c037eb681c8c2fe1ab6f30 Mon Sep 17 00:00:00 2001 From: angrykoala Date: Wed, 31 Jan 2024 16:44:10 +0100 Subject: [PATCH 13/23] Apply suggestions from code review Co-authored-by: Lidia Zuin <102308961+lidiazuin@users.noreply.github.com> --- modules/ROOT/pages/migration/index.adoc | 20 ++++--------------- .../ROOT/pages/subscriptions/filtering.adoc | 2 +- .../type-definitions/types/interfaces.adoc | 5 +++-- .../type-definitions/types/relationships.adoc | 2 +- 4 files changed, 9 insertions(+), 20 deletions(-) diff --git a/modules/ROOT/pages/migration/index.adoc b/modules/ROOT/pages/migration/index.adoc index 2313587b..c07d75ca 100644 --- a/modules/ROOT/pages/migration/index.adoc +++ b/modules/ROOT/pages/migration/index.adoc @@ -20,12 +20,8 @@ Here is a list of all the breaking changes from version 4.0.0 to 5.0.0. === `@relationshipProperties` -<<<<<<< HEAD The directive `@relationshipProperties` should now be used on types rather than interfaces. For example: -======= -The directive `@relationshipProperties` should now be used on types rather than interfaces: ->>>>>>> 20f6bef (Migration guide to 5.0.0) [cols="1,1"] |=== @@ -78,13 +74,9 @@ This also applies to custom directives. The `@relationship` directive is no longer available in interfaces. If you need a relationship to be available on interfaces, you need to use the new `@declareRelationship` directive instead, as well as define the relationships in the concrete type. -<<<<<<< HEAD This change is due to directives no longer cascading from interfaces to types. However, now it is possible for a relationship to have different properties and labels in each type. For example: -======= -This is due to directives no longer cascading from interfaces to types and allows for a relationship to have different properties and labels in each type. ->>>>>>> 20f6bef (Migration guide to 5.0.0) [cols="1,1"] |=== @@ -178,11 +170,7 @@ query ActedInConnection { === `_on` filters deprecated -<<<<<<< HEAD `_on` filters for interfaces are no longer available in `where` and mutations. To filter by an implementing type, you need to use the new filter `typename_IN`: -======= -`_on` filters for interfaces are no longer available in `where` and mutations. To filter by a implementing type you need to use the new filter `typename_IN`: ->>>>>>> 20f6bef (Migration guide to 5.0.0) [cols="1,1"] @@ -222,10 +210,10 @@ query MyQuery { Using fields of a type in an interface operation is no longer supported. ==== -=== Minimum Node updated to `18.0.0` +==== Minimum NodeJS version -With the deprecation of Node 16, the minimum supported NodeJS version is `18.0.0` +With the deprecation of Node 16, the minimum supported NodeJS version is `18.0.0`. -=== `experimental` flag removed +==== `experimental` flag -The `experimental` flag is no longer available on the options of `Neo4jGraphQL` class. +The `experimental` flag is no longer available in the options of the `Neo4jGraphQL` class. \ No newline at end of file diff --git a/modules/ROOT/pages/subscriptions/filtering.adoc b/modules/ROOT/pages/subscriptions/filtering.adoc index fe542a92..e9af7354 100644 --- a/modules/ROOT/pages/subscriptions/filtering.adoc +++ b/modules/ROOT/pages/subscriptions/filtering.adoc @@ -751,4 +751,4 @@ The following example illustrates how to filter the node at the other end of the } ---- -This example returns events for relationships between the type `Movie` and `Reviewer`, where the score is higher than 7, and the `Reviewer` has a reputation greater or equal to 7 +This example returns events for relationships between the type `Movie` and `Reviewer`, where the score is higher than 7, and the `Reviewer` has a reputation greater or equal to 7. diff --git a/modules/ROOT/pages/type-definitions/types/interfaces.adoc b/modules/ROOT/pages/type-definitions/types/interfaces.adoc index ec201a3f..bc97124f 100644 --- a/modules/ROOT/pages/type-definitions/types/interfaces.adoc +++ b/modules/ROOT/pages/type-definitions/types/interfaces.adoc @@ -8,7 +8,8 @@ This page describes how to use and define interfaces on relationship fields. == Creating an interface field -The following schema defines a `Actor` type, that has a relationship `ACTED_IN`, of type `[Production!]!`. `Production` is an interface type with `Movie` and `Series` implementations. +The following schema defines an `Actor` type, that has a relationship `ACTED_IN`, of type `[Production!]!`. +`Production` is an interface type with `Movie` and `Series` implementations. [source, graphql, indent=0] ---- @@ -64,7 +65,7 @@ query GetProductionsStartingWithThe { } ---- -The query below will only return the movies with title starting with "The " for each actor by filtering by `typename_IN`. +The following query will only return the movies with title starting with "The " for each actor by filtering them by `typename_IN`: [source, graphql, indent=0] ---- diff --git a/modules/ROOT/pages/type-definitions/types/relationships.adoc b/modules/ROOT/pages/type-definitions/types/relationships.adoc index a3d3e239..f32f6f0a 100644 --- a/modules/ROOT/pages/type-definitions/types/relationships.adoc +++ b/modules/ROOT/pages/type-definitions/types/relationships.adoc @@ -62,7 +62,7 @@ This means that a `Movie` *must* have a `DIRECTED` relationship coming into it t You can add relationship properties to the example in two steps: -. Add a type definition decorated with the `@relationshipProperties` directive and containing the desired relationship properties. +. Add a type definition decorated with the `@relationshipProperties` directive, containing the desired relationship properties. . Add a `properties` argument to both "sides" (or just one side, if you prefer) of the `@relationship` directive which points to the newly defined interface. For example, suppose you want to distinguish which roles an actor played in a movie: From 92bc35705949854f362001cc0948b010d500a35e Mon Sep 17 00:00:00 2001 From: angrykoala Date: Thu, 1 Feb 2024 12:59:51 +0100 Subject: [PATCH 14/23] Rever page aliases changes in troubleshooting --- modules/ROOT/pages/troubleshooting.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/pages/troubleshooting.adoc b/modules/ROOT/pages/troubleshooting.adoc index 95e268cf..4ea5e87c 100644 --- a/modules/ROOT/pages/troubleshooting.adoc +++ b/modules/ROOT/pages/troubleshooting.adoc @@ -1,6 +1,6 @@ [[troubleshooting]] = Troubleshooting -:page-aliases: troubleshooting/faqs.adoc, troubleshooting/security.adoc, troubleshooting/optimizing-create-operations.adoc, appendix.adoc +:page-aliases: troubleshooting/faqs.adoc, troubleshooting/security.adoc, troubleshooting/optimizing-create-operations.adoc, appendix/preventing-overfetching.adoc, appendix.adoc This chapter contains common troubleshooting steps. Additionally, there is a section for xref::troubleshooting.adoc#troubleshooting-faqs[FAQs] (Frequently Asked Questions) where you might find answers to your problems. From 4affb8bfafe5e032d38b81b11f8d6413b8b301a3 Mon Sep 17 00:00:00 2001 From: Michael Webb <28074382+mjfwebb@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:36:51 +0100 Subject: [PATCH 15/23] 5.x updates (#106) * fix: change Number to Int * fix: change interface to type for relationshipProperties * fix: change reference of _on to typename_ * feat: add declareRelationship to directives reference * feat: add @declareRelationship to interface example code * feat: add example usage of declareRelationship to relationships page * fix: typename_IN --- modules/ROOT/pages/mutations/update.adoc | 4 ++- .../pages/queries-aggregations/filtering.adoc | 2 +- modules/ROOT/pages/subscriptions/events.adoc | 2 +- .../type-definitions/directives/index.adoc | 5 ++- .../type-definitions/types/interfaces.adoc | 2 +- .../type-definitions/types/relationships.adoc | 33 +++++++++++++++++++ 6 files changed, 43 insertions(+), 5 deletions(-) diff --git a/modules/ROOT/pages/mutations/update.adoc b/modules/ROOT/pages/mutations/update.adoc index 1c2a06bf..906e5b78 100644 --- a/modules/ROOT/pages/mutations/update.adoc +++ b/modules/ROOT/pages/mutations/update.adoc @@ -448,11 +448,13 @@ type Video { views: Int ownedBy: User @relationship(type: "OWN_VIDEO", properties: "OwnVideo", direction: IN) } + type User { id: ID @id ownVideo: [Video!]! @relationship(type: "OWN_VIDEO", properties: "OwnVideo", direction: OUT) } -interface OwnVideo @relationshipProperties { + +type OwnVideo @relationshipProperties { revenue: Float } ---- diff --git a/modules/ROOT/pages/queries-aggregations/filtering.adoc b/modules/ROOT/pages/queries-aggregations/filtering.adoc index eb77fbf3..938abaa1 100644 --- a/modules/ROOT/pages/queries-aggregations/filtering.adoc +++ b/modules/ROOT/pages/queries-aggregations/filtering.adoc @@ -213,7 +213,7 @@ query { == Querying an interface -You can use the `_on` argument to filter interfaces. +You can use the `typename_IN` filter to filter interfaces. Refer to xref:type-definitions/types/interfaces.adoc#type-definitions-interfaced-types-querying[Type definitions -> Type -> Interface] for more details and an example. == Relationship filtering diff --git a/modules/ROOT/pages/subscriptions/events.adoc b/modules/ROOT/pages/subscriptions/events.adoc index ac48f133..5d298435 100644 --- a/modules/ROOT/pages/subscriptions/events.adoc +++ b/modules/ROOT/pages/subscriptions/events.adoc @@ -188,7 +188,7 @@ type Reviewer { reputation: Int } -interface Reviewed @relationshipProperties { +type Reviewed @relationshipProperties { score: Int! } ---- diff --git a/modules/ROOT/pages/type-definitions/directives/index.adoc b/modules/ROOT/pages/type-definitions/directives/index.adoc index d91fa943..3436b145 100644 --- a/modules/ROOT/pages/type-definitions/directives/index.adoc +++ b/modules/ROOT/pages/type-definitions/directives/index.adoc @@ -27,6 +27,9 @@ of any required fields that is passed as arguments to the custom resolver. | xref::/type-definitions/directives/cypher.adoc[`@cypher`] | Overrides field resolution (including `Query` and `Mutation` fields), instead resolving with the specified Cypher. +| xref::/schema-configuration/field-configuration.adoc#_relationship[`@declareRelationship`] +| Configure xref::/type-definitions/types/relationships.adoc[relationships] to be implemented on object types. + | xref::/type-definitions/directives/default-values.adoc#type-definitions-default-values-default[`@default`] | Allows for the setting of a default value for a field on object creation. @@ -98,4 +101,4 @@ directive @relationshipProperties on INTERFACE | xref::/type-definitions/directives/indexes-and-constraints.adoc#type-definitions-constraints-unique[`@unique`] | Indicates that there should be a uniqueness constraint in the database for the fields that it is applied to. -|=== \ No newline at end of file +|=== diff --git a/modules/ROOT/pages/type-definitions/types/interfaces.adoc b/modules/ROOT/pages/type-definitions/types/interfaces.adoc index bc97124f..a85af4cb 100644 --- a/modules/ROOT/pages/type-definitions/types/interfaces.adoc +++ b/modules/ROOT/pages/type-definitions/types/interfaces.adoc @@ -15,7 +15,7 @@ The following schema defines an `Actor` type, that has a relationship `ACTED_IN` ---- interface Production { title: String! - actors: [Actor!]! + actors: [Actor!]! @declareRelationship } type Movie implements Production { diff --git a/modules/ROOT/pages/type-definitions/types/relationships.adoc b/modules/ROOT/pages/type-definitions/types/relationships.adoc index f32f6f0a..e66b8539 100644 --- a/modules/ROOT/pages/type-definitions/types/relationships.adoc +++ b/modules/ROOT/pages/type-definitions/types/relationships.adoc @@ -88,6 +88,39 @@ type ActedIn @relationshipProperties { } ---- +If you need a relationship to be available on interfaces, you need to use the new `@declareRelationship` directive instead, as well as define the relationships in the concrete type: + +[source, graphql, indent=0] +---- +interface Production { + title: String! + actors: [Actor!]! @declareRelationship +} + +type Actor { + name: String! + born: Int! + actedInMovies: [Movie!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: OUT) +} + +type Movie { + title: String! + released: Int! + actors: [Actor!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN) +} + +type Series { + title: String! + released: Int! + episodes: Int! + actors: [Actor!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN) +} + +type ActedIn @relationshipProperties { + roles: [String!] +} +---- + === `queryDirection` All relationships have a direction. From c50c4ef30a884af1617526a77edd6a0e7f8784c4 Mon Sep 17 00:00:00 2001 From: Lidia Zuin <102308961+lidiazuin@users.noreply.github.com> Date: Fri, 23 Feb 2024 14:08:18 +0100 Subject: [PATCH 16/23] 5.x publish (#112) * Standardise auth variables #93 (#95) * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the latest additions (#34) * Update publish.yml * Editorial review of the most recent changes * reverting changes to partials * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * Add page-aliases for version 4 (#31) * revert * Apply suggestions from code review Co-authored-by: Michael Webb <28074382+mjfwebb@users.noreply.github.com> --------- Co-authored-by: Neil Dewhurst Co-authored-by: Michael Webb <28074382+mjfwebb@users.noreply.github.com> * Fix scalar description placeholders #38 (#39) * Editorial review of the most recent changes * reverting changes to partials * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * fixing note formatting * Editorial review of the most recent changes * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * Fix scalar description placeholders (#38) --------- Co-authored-by: Neil Dewhurst Co-authored-by: Darrell Warde <8117355+darrellwarde@users.noreply.github.com> * Editorial review of the most recent changes * reverting changes to partials * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * Editorial review of the most recent changes * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * Standardise auth variables (#93) * docs: use "username" in where username would be used in code examples * docs: fix typos * docs: remove part about creating a .env file --------- Co-authored-by: Neil Dewhurst Co-authored-by: Michael Webb <28074382+mjfwebb@users.noreply.github.com> Co-authored-by: Darrell Warde <8117355+darrellwarde@users.noreply.github.com> * Removing images that are not being used anywhere in the docs #98 (#99) * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the latest additions (#34) * Update publish.yml * Editorial review of the most recent changes * reverting changes to partials * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * Add page-aliases for version 4 (#31) * revert * Apply suggestions from code review Co-authored-by: Michael Webb <28074382+mjfwebb@users.noreply.github.com> --------- Co-authored-by: Neil Dewhurst Co-authored-by: Michael Webb <28074382+mjfwebb@users.noreply.github.com> * Fix scalar description placeholders #38 (#39) * Editorial review of the most recent changes * reverting changes to partials * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * fixing note formatting * Editorial review of the most recent changes * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * Fix scalar description placeholders (#38) --------- Co-authored-by: Neil Dewhurst Co-authored-by: Darrell Warde <8117355+darrellwarde@users.noreply.github.com> * Editorial review of the most recent changes * reverting changes to partials * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * Editorial review of the most recent changes * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * Removing images that are not being used anywhere in the docs (#98) * Cherry-pick * reverting changes * reverting changes * reverting changes --------- Co-authored-by: Neil Dewhurst Co-authored-by: Michael Webb <28074382+mjfwebb@users.noreply.github.com> Co-authored-by: Darrell Warde <8117355+darrellwarde@users.noreply.github.com> * Removing page alias that was causing a loop #102 (#103) * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the latest additions (#34) * Update publish.yml * Editorial review of the most recent changes * reverting changes to partials * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * Add page-aliases for version 4 (#31) * revert * Apply suggestions from code review Co-authored-by: Michael Webb <28074382+mjfwebb@users.noreply.github.com> --------- Co-authored-by: Neil Dewhurst Co-authored-by: Michael Webb <28074382+mjfwebb@users.noreply.github.com> * Fix scalar description placeholders #38 (#39) * Editorial review of the most recent changes * reverting changes to partials * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * fixing note formatting * Editorial review of the most recent changes * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * Fix scalar description placeholders (#38) --------- Co-authored-by: Neil Dewhurst Co-authored-by: Darrell Warde <8117355+darrellwarde@users.noreply.github.com> * Editorial review of the most recent changes * reverting changes to partials * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * Editorial review of the most recent changes * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * Removing page alias that was causing a loop (#102) * fix --------- Co-authored-by: Neil Dewhurst Co-authored-by: Michael Webb <28074382+mjfwebb@users.noreply.github.com> Co-authored-by: Darrell Warde <8117355+darrellwarde@users.noreply.github.com> * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the latest additions (#34) * Update publish.yml * Editorial review of the most recent changes * reverting changes to partials * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * Add page-aliases for version 4 (#31) * revert * Apply suggestions from code review Co-authored-by: Michael Webb <28074382+mjfwebb@users.noreply.github.com> --------- Co-authored-by: Neil Dewhurst Co-authored-by: Michael Webb <28074382+mjfwebb@users.noreply.github.com> * Fix scalar description placeholders #38 (#39) * Editorial review of the most recent changes * reverting changes to partials * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * fixing note formatting * Editorial review of the most recent changes * fixing note formatting * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * Fix scalar description placeholders (#38) --------- Co-authored-by: Neil Dewhurst Co-authored-by: Darrell Warde <8117355+darrellwarde@users.noreply.github.com> * Editorial review of the most recent changes * reverting changes to partials * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * Editorial review of the most recent changes * Add page-aliases for version 4 (#31) (#32) Co-authored-by: Neil Dewhurst * Editorial review of the most recent changes * Publish 5.x docs * fixing content * fixing content * reverting changes to match 5.x actual content --------- Co-authored-by: Neil Dewhurst Co-authored-by: Michael Webb <28074382+mjfwebb@users.noreply.github.com> Co-authored-by: Darrell Warde <8117355+darrellwarde@users.noreply.github.com> --- .../impersonation-and-user-switching.adoc | 2 +- modules/ROOT/pages/subscriptions/events.adoc | 2 +- modules/ROOT/pages/subscriptions/filtering.adoc | 2 +- modules/ROOT/pages/troubleshooting.adoc | 2 +- publish.yml | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/ROOT/pages/authentication-and-authorization/impersonation-and-user-switching.adoc b/modules/ROOT/pages/authentication-and-authorization/impersonation-and-user-switching.adoc index 0bbb164c..34ec4863 100644 --- a/modules/ROOT/pages/authentication-and-authorization/impersonation-and-user-switching.adoc +++ b/modules/ROOT/pages/authentication-and-authorization/impersonation-and-user-switching.adoc @@ -200,4 +200,4 @@ const { url } = await startStandaloneServer(server, { console.log(`🚀 Server ready at: ${url}`); ---- ===== -==== +==== \ No newline at end of file diff --git a/modules/ROOT/pages/subscriptions/events.adoc b/modules/ROOT/pages/subscriptions/events.adoc index 5d298435..60af465b 100644 --- a/modules/ROOT/pages/subscriptions/events.adoc +++ b/modules/ROOT/pages/subscriptions/events.adoc @@ -1826,4 +1826,4 @@ Had we subscribed to `Person` as well, we would have received two more events: } } }, ----- +---- \ No newline at end of file diff --git a/modules/ROOT/pages/subscriptions/filtering.adoc b/modules/ROOT/pages/subscriptions/filtering.adoc index e9af7354..da5592f0 100644 --- a/modules/ROOT/pages/subscriptions/filtering.adoc +++ b/modules/ROOT/pages/subscriptions/filtering.adoc @@ -751,4 +751,4 @@ The following example illustrates how to filter the node at the other end of the } ---- -This example returns events for relationships between the type `Movie` and `Reviewer`, where the score is higher than 7, and the `Reviewer` has a reputation greater or equal to 7. +This example returns events for relationships between the type `Movie` and `Reviewer`, where the score is higher than 7, and the `Reviewer` has a reputation greater or equal to 7. \ No newline at end of file diff --git a/modules/ROOT/pages/troubleshooting.adoc b/modules/ROOT/pages/troubleshooting.adoc index 4ea5e87c..17289464 100644 --- a/modules/ROOT/pages/troubleshooting.adoc +++ b/modules/ROOT/pages/troubleshooting.adoc @@ -217,4 +217,4 @@ This section describes security considerations and known issues. If a query yields no results, the xref::authentication-and-authorization/authorization.adoc[Authorization] process will not be triggered. This means that the result will be empty, instead of throwing an authentication error. Unauthorized users may -then discern whether or not a certain type exists in the database, even if data itself cannot be accessed. +then discern whether or not a certain type exists in the database, even if data itself cannot be accessed. \ No newline at end of file diff --git a/publish.yml b/publish.yml index c2f154a7..0d282963 100644 --- a/publish.yml +++ b/publish.yml @@ -25,13 +25,13 @@ urls: antora: extensions: - require: "@neo4j-antora/antora-modify-sitemaps" - sitemap_version: '4' + sitemap_version: '5' sitemap_loc_version: 'current' move_sitemaps_to_components: true - require: "@neo4j-antora/aliases-redirects" redirect_format: neo4j redirect_map: - - from: '4' + - from: '5' to: 'current' asciidoc: From 247918ac61236667958f2685fb89ab0883bc692e Mon Sep 17 00:00:00 2001 From: angrykoala Date: Tue, 27 Feb 2024 14:51:06 +0100 Subject: [PATCH 17/23] Remove example on custom resolvers in interfaces --- modules/ROOT/pages/custom-resolvers.adoc | 48 ------------------------ 1 file changed, 48 deletions(-) diff --git a/modules/ROOT/pages/custom-resolvers.adoc b/modules/ROOT/pages/custom-resolvers.adoc index 1e07b854..5ce8d914 100644 --- a/modules/ROOT/pages/custom-resolvers.adoc +++ b/modules/ROOT/pages/custom-resolvers.adoc @@ -54,54 +54,6 @@ directive @customResolver( ) on FIELD_DEFINITION ---- -=== Providing custom resolvers - -Note that any field marked with the `@customResolver` directive requires a custom resolver to be defined. -If the directive is marked on an interface, any implementation of that interface requires a custom resolver to be defined. - -Take for example this schema: - -[source, graphql, indent=0] ----- -interface UserInterface { - fullName: String! @customResolver -} - -type User implements UserInterface { - id: ID! - fullName: String! -} ----- - -The following resolvers definition would cause a warning to be logged: - -[source, javascript, indent=0] ----- -const resolvers = { - UserInterface: { - fullName() { - return "Hello World!"; - }, - }, -}; ----- - -The following resolvers definition would silence the warning: - -[source, javascript, indent=0] ----- -const resolvers = { - User: { - fullName() { - return "Hello World!"; - }, - }, -}; ----- - -Mismatches between the resolver map and `@customResolver` directives are always logged to the console as a warning. - - === The `requires` argument This is how the `requires` argument can be used on your schema: From 248ef444fff8d90b3e14f1e16609f21a0e19fffc Mon Sep 17 00:00:00 2001 From: Darrell Warde <8117355+darrellwarde@users.noreply.github.com> Date: Tue, 27 Feb 2024 14:22:56 +0000 Subject: [PATCH 18/23] Update relationships.adoc --- modules/ROOT/pages/type-definitions/types/relationships.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ROOT/pages/type-definitions/types/relationships.adoc b/modules/ROOT/pages/type-definitions/types/relationships.adoc index e66b8539..70b20ded 100644 --- a/modules/ROOT/pages/type-definitions/types/relationships.adoc +++ b/modules/ROOT/pages/type-definitions/types/relationships.adoc @@ -103,13 +103,13 @@ type Actor { actedInMovies: [Movie!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: OUT) } -type Movie { +type Movie implements Production { title: String! released: Int! actors: [Actor!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN) } -type Series { +type Series implements Production { title: String! released: Int! episodes: Int! From 82870d60254429ee6ea4cf359b9adf7158e57db2 Mon Sep 17 00:00:00 2001 From: angrykoala Date: Thu, 29 Feb 2024 11:27:21 +0000 Subject: [PATCH 19/23] Remove Migration to version 4 in documentation for 5.x --- modules/ROOT/content-nav.adoc | 3 - .../index.adoc | 7 +- .../pages/migration/4.0.0/authorization.adoc | 313 ----- modules/ROOT/pages/migration/4.0.0/index.adoc | 1012 ----------------- modules/ROOT/pages/migration/4.0.0/ogm.adoc | 50 - 5 files changed, 1 insertion(+), 1384 deletions(-) delete mode 100644 modules/ROOT/pages/migration/4.0.0/authorization.adoc delete mode 100644 modules/ROOT/pages/migration/4.0.0/index.adoc delete mode 100644 modules/ROOT/pages/migration/4.0.0/ogm.adoc diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index e1df5753..9ffcedcc 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -58,9 +58,6 @@ ** xref:introspector.adoc[Introspector] ** xref:migration/index.adoc[Migration guide] -*** xref:migration/4.0.0/index.adoc[4.0.0 Guide] -**** xref:migration/4.0.0/authorization.adoc[] -**** xref:migration/4.0.0/ogm.adoc[] ** xref:ogm/index.adoc[] *** xref:ogm/installation.adoc[] diff --git a/modules/ROOT/pages/authentication-and-authorization/index.adoc b/modules/ROOT/pages/authentication-and-authorization/index.adoc index c27729a0..f64b8450 100644 --- a/modules/ROOT/pages/authentication-and-authorization/index.adoc +++ b/modules/ROOT/pages/authentication-and-authorization/index.adoc @@ -5,14 +5,9 @@ auth/authorization.adoc, auth/auth-directive.adoc, auth/subscriptions.adoc, \ auth/authorization/allow.adoc, auth/authorization/bind.adoc, auth/authorization/roles.adoc, \ auth/authorization/where.adoc, guides/v4-migration/authorization.adoc -[WARNING] -==== -The `@auth` directive has been replaced by `@authentication` and `@authorization`. -See the xref::migration/v4-migration/authorization.adoc[Migration guide] for details on how to upgrade. -==== * xref::authentication-and-authorization/authentication.adoc[Authentication] - Explicit authentication, configured using the `@authentication` directive. * xref::authentication-and-authorization/authorization.adoc[Authorization] - Authorization rules set using the `@authorization` directive. * xref::authentication-and-authorization/configuration.adoc[Configuration] - Instructions to set up instantiation. * xref::authentication-and-authorization/impersonation-and-user-switching.adoc[Impersonation and user switching] - How to set up impersonation and user switching features. -* xref::authentication-and-authorization/reference/operations.adoc[Operations] - Reference on GraphQL queries and how each location in each query triggers the evaluation of different authentication/authorization rules. \ No newline at end of file +* xref::authentication-and-authorization/reference/operations.adoc[Operations] - Reference on GraphQL queries and how each location in each query triggers the evaluation of different authentication/authorization rules. diff --git a/modules/ROOT/pages/migration/4.0.0/authorization.adoc b/modules/ROOT/pages/migration/4.0.0/authorization.adoc deleted file mode 100644 index 561b6f04..00000000 --- a/modules/ROOT/pages/migration/4.0.0/authorization.adoc +++ /dev/null @@ -1,313 +0,0 @@ -= Authentication and Authorization -:description: This page describes the changes in authentication and authorization features in version 4.0.0 of the Neo4j GraphQL Library. -:page-aliases: auth/global-authentication.adoc, migration/v4-migration/authorization.adoc - -The largest breaking change in version 4.0.0 is the removal of the `@auth` directive, which requires a migration to the new `@authentication`, `@authorization`, and `@subscriptionsAuthorization` directives. - -== Instantiation - -While the `@auth` directive required the installation of an additional plugin package, the functionality for the new directives is now built directly into the library. - -To start using it, you should uninstall the previous plugin: - -[source, bash, indent=0] ----- -npm uninstall @neo4j/graphql-plugin-auth ----- - -=== Symmetric secret - -Given this example of instantiation using a symmetric secret with the plugin: - -[source, typescript, indent=0] ----- -new Neo4jGraphQL({ - typeDefs, - plugins: { - auth: new Neo4jGraphQLAuthJWTPlugin({ - secret: "secret", - }), - } -}) ----- - -You can delete the import of `Neo4jGraphQLAuthJWTPlugin` and change the instantiation to: - -[source, typescript, indent=0] ----- -new Neo4jGraphQL({ - typeDefs, - features: { - authorization: { - key: "secret", - } - } -}) ----- - -=== JWKS endpoint - -When using a JWKS endpoint, an example of how this might be configured currently is: - -[source, typescript, indent=0] ----- -new Neo4jGraphQL({ - typeDefs, - plugins: { - auth: new Neo4jGraphQLAuthJWKSPlugin({ - jwksEndpoint: "https://YOUR_DOMAIN/well-known/jwks.json", - }), - } -}) ----- - -In version 4.0.0, delete the import of `Neo4jGraphQLAuthJWKSPlugin`, and change the instantiation to: - -[source, typescript, indent=0] ----- -new Neo4jGraphQL({ - typeDefs, - features: { - authorization: { - key: { - url: "https://YOUR_DOMAIN/well-known/jwks.json", - }, - } - } -}) ----- - -== Server - -Previously, you could pass in the entire request object and the library would find the `Authorization` header: - -[source, typescript, indent=0] ----- -const server = new ApolloServer({ - schema, // schema from Neo4jGraphQL.getSchema() -}); - -const { url } = await startStandaloneServer(server, { - listen: { port: 4000 }, - context: async ({ req }) => ({ req }), -}); ----- - -With the new implementation, the library expects the `Authorization` header to be extracted in the format of a bearer token in the `token` field of the context: - -[source, typescript, indent=0] ----- -const server = new ApolloServer({ - schema, // schema from Neo4jGraphQL.getSchema() -}); - -const { url } = await startStandaloneServer(server, { - listen: { port: 4000 }, - context: async ({ req }) => ({ - token: req.headers.authorization, - }), -}); ----- - -This is to acknowledge the fact that there are a variety of servers which don't have a `req` object (such as serverless functions, which use `event`). - -=== `rolesPath` - -The `rolesPath` argument was used to configure a custom path for the "roles" claim in the JWT structure. -This configuration has now been moved into the type definitions themselves. - -Given a previous instantiation: - -[source, typescript, indent=0] ----- -new Neo4jGraphQL({ - typeDefs, - plugins: { - auth: new Neo4jGraphQLAuthJWTPlugin({ - secret: "secret", - rolesPath: "some.nested.path", - }), - } -}) ----- - -This now needs to instead be configured in the type definitions as: - -[source, graphql, indent=0] ----- -type JWT @jwt { - roles: [String!]! @jwtClaim(path: "some.nested.path") -} ----- - -The type name itself can be anything, as long as it is decorated by `@jwt`. - -Despite the extra setup steps required in 4.0.0, the strongly typed nature of the definition means there is now significantly more powerful filtering options. - -== Global authentication - -Global authentication was previously configured in the auth plugin constructor, for instance: - -[source, typescript, indent=0] ----- -new Neo4jGraphQL({ - typeDefs, - plugins: { - auth: new Neo4jGraphQLAuthJWTPlugin({ - secret: "secret", - globalAuthentication: true, - }), - } -}) ----- - -To remain consistent with the use of directives for configuration, this is now achieved in type definitions by extending the schema: - -[source, graphql, indent=0] ----- -extend schema @authentication ----- - -== Rules - -=== `allow` - -Given an `allow` rule, which checks the `id` field of a `User` against the JWT subject _before_ any operation: - -[source, graphql, indent=0] ----- -type User @auth(rules: [{ allow: { id: "$jwt.sub" } }]) { - id: ID! -} ----- - -This is now: - -[source, graphql, indent=0] ----- -type User @authorization(validate: [{ when: [BEFORE], where: { node: { id: "$jwt.sub" } } }]) { - id: ID! -} ----- - -Note that `allow` is no longer a discrete rule, but configured by a `when` argument, which is an array accepting the values `BEFORE` and `AFTER`. - -=== `bind` - -Given a `bind` rule, which checks the `id` field of a `User` against the JWT subject _after_ any operation: - -[source, graphql, indent=0] ----- -type User @auth(rules: [{ bind: { id: "$jwt.sub" } }]) { - id: ID! -} ----- - -This is now: - -[source, graphql, indent=0] ----- -type User @authorization(validate: [{ when: [AFTER], where: { node: { id: "$jwt.sub" } } }]) { - id: ID! -} ----- - -Note that `bind` is no longer a discrete rule, but configured by a `when` argument which is an array accepting values `BEFORE` and `AFTER`. - -=== `isAuthenticated` - -[WARNING] -==== -There isn't a direct replacement for the `isAuthenticated` argument. -https://github.com/neo4j/graphql/issues/new/choose[Raise a feature request] if this is blocking migration. -==== - -Given a previous type definition, which required authentication for any operation on the type `User`: - -[source, graphql, indent=0] ----- -type User @auth(rules: [{ isAuthenticated: true }]) { - id: ID! -} ----- - -There is not a rule under `@authorization` anymore, but the closest is: - -[source, graphql, indent=0] ----- -type User @authentication { - id: ID! -} ----- - -The difference here being that, for example, given the following query: - -[source, graphql, indent=0] ----- -{ - users(where: { id: "1" }) { - id - } -} ----- - -* `@auth(rules: [{ isAuthenticated: true }])` only throws an error if the `where: { id: "1" }` filter results in a match on a `User`. -* `@authentication` always throws an error if a user is not authenticated. -This happens _before_ the database execution in order to restrict database access to queries generated by authenticated users only. - -=== `roles` - -For these examples, the following is required in the type definitions: - -[source, graphql, indent=0] ----- -type JWT @jwt { - roles: [String!]! -} ----- - -Given the following type definition, which requires a user to have the `admin` role to perform any operation on the type `User`: - -[source, graphql, indent=0] ----- -type User @auth(rules: [{ roles: "admin" }]) { - id: ID! -} ----- - -This is now: - -[source, graphql, indent=0] ----- -type User @authorization(validate: [{ where: { jwt: { roles_INCLUDES: "admin" } } }]) { - id: ID! -} ----- - -The following changes were made for this migration: - -* If a `validate` rule has been used, it throws an error without the role as per the `roles` argument in the `@auth` directive. -This can alternatively be a `filter` rule to just return zero results if a user does not have the required role. -* `roles` has become `roles_INCLUDES`, because the xref::queries-aggregations/filtering.adoc[full filtering capabilities of the library] can now be used within the `@authorization` directive. -* `roles` is no longer a top-level rule field, but nested within `where`, under `jwt`. -Any number of JWT claims can now be compared against, if configured within the type decorated with `@jwt`. - -=== `where` - -It replaces an `@auth` rule which would have previously looked like: - -[source, graphql, indent=0] ----- -type User @auth(rules: [{ where: { id: "$jwt.sub" } }]) { - id: ID! -} ----- - -And now the `@authorization` directive must be: - -[source, graphql, indent=0] ----- -type User @authorization(filter: [{ where: { node: { id: "$jwt.sub" } } }]) { - id: ID! -} ----- diff --git a/modules/ROOT/pages/migration/4.0.0/index.adoc b/modules/ROOT/pages/migration/4.0.0/index.adoc deleted file mode 100644 index 72b159bb..00000000 --- a/modules/ROOT/pages/migration/4.0.0/index.adoc +++ /dev/null @@ -1,1012 +0,0 @@ -[[v4-migration]] -:description: This page lists the breaking changes from version 3.0.0 to 4.0.0 and describes how to update. -:page-aliases: guides/v4-migration/index.adoc , migration/v4-migration/index.adoc -= Migration to 4.0.0 - -This page lists all breaking changes from the Neo4j GraphQL Library version 3.x to 4.x and how to update it. - -== How to update - -To update your Neo4j GraphQL Library, use npm or the package manager of choice: - -[source, bash, indent=0] ----- -npm update @neo4j/graphql ----- - -== Breaking changes - -Here is a list of all the breaking changes from version 3.0.0 to 4.0.0. - -=== `IExecutableSchemaDefinition` - -If you were passing any arguments from https://the-guild.dev/graphql/tools/docs/api/interfaces/schema_src.iexecutableschemadefinition[`IExecutableSchemaDefinition`] into the library other than `typeDefs` and `resolvers`, these are no longer supported. - -=== `config.enableDebug` - -The programmatic toggle for debug logging has been moved from `config.enableDebug` to simply `debug`. - -[cols="1,1"] -|=== -|Before | Now - -a| -[source, javascript, indent=0] ----- -const { Neo4jGraphQL } = require("@neo4j/graphql"); -const neo4j = require("neo4j-driver"); -const { ApolloServer } = require("apollo-server"); - -const typeDefs = ` - type Movie { - title: String! - } -`; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("neo4j", "password") -); - -const neoSchema = new Neo4jGraphQL({ - typeDefs, - driver, - config: { - enableDebug: true, - } -}); ----- -a| -[source, javascript, indent=0] ----- -const { Neo4jGraphQL } = require("@neo4j/graphql"); -const neo4j = require("neo4j-driver"); -const { ApolloServer } = require("apollo-server"); - -const typeDefs = ` - type Movie { - title: String! - } -`; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("neo4j", "password") -); - -const neoSchema = new Neo4jGraphQL({ - typeDefs, - driver, - debug: true, -}); ----- -|=== - -=== `driverConfig` - -Session configuration is now available only in the context under the `sessionConfig` key. -Additionally, the `bookmarks` key has been removed as it is no longer needed with the bookmark manager of the newer driver. - -[cols="1,1"] -|=== -|Before | Now - -a| -[source, javascript, indent=0] ----- -import { ApolloServer } from '@apollo/server'; -import { startStandaloneServer } from '@apollo/server/standalone'; -import { Neo4jGraphQL } from "@neo4j/graphql"; -import neo4j from "neo4j-driver"; - -const typeDefs = `#graphql - type User { - name: String - } -`; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("neo4j", "password") -); - -const neoSchema = new Neo4jGraphQL({ - typeDefs, - config: { - driverConfig: { - database: "different-db" - }, - }, -}) - -const server = new ApolloServer({ - schema: await neoSchema.getSchema(), -}); - -await startStandaloneServer(server, { - context: async ({ req }) => ({ req }), -}); - ----- -a| -[source, javascript, indent=0] ----- -import { ApolloServer } from '@apollo/server'; -import { startStandaloneServer } from '@apollo/server/standalone'; -import { Neo4jGraphQL } from "@neo4j/graphql"; -import neo4j from "neo4j-driver"; - -const typeDefs = `#graphql - type User { - name: String - } -`; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("neo4j", "password") -); - -const neoSchema = new Neo4jGraphQL({ typeDefs, driver }); - -const server = new ApolloServer({ - schema: await neoSchema.getSchema(), -}); - -await startStandaloneServer(server, { - context: async ({ req }) => ({ sessionConfig: { database: "my-database" }}), -}); ----- -|=== - -=== `config.enableRegex` - -This has been replaced by `MATCHES` in features.filters. -With this change comes more granularity in the feature configuration. -You can now enable the `MATCHES` filter on `String` and `ID` fields separately: - -[cols="1,1"] -|=== -|Before | After - -a| -[source, javascript, indent=0] ----- -neoSchema = new Neo4jGraphQL({ - typeDefs, - config: { - enableRegex: true - } -}); ----- -a| -[source, javascript, indent=0] ----- -neoSchema = new Neo4jGraphQL({ - typeDefs, - features: { - filters: { - String: { - MATCHES: true, - }, - ID: { - MATCHES: true, - }, - }, - }, -}); ----- -|=== - -=== `queryOptions` - -If you had a need to pass in Cypher query options for query tuning, this interface has been changed. -The config option `queryOptions` has now become `cypherQueryOptions` inside the context function, and it now accepts simple strings instead of enums. -This change reflects the fact that the Cypher query options are set on a per-request basis. - -[cols="1,1"] -|=== -|Before | Now - -a| -[source, javascript, indent=0] ----- -const { Neo4jGraphQL, CypherRuntime } = require("@neo4j/graphql"); -const { ApolloServer } = require("apollo-server"); - -const typeDefs = ` - type Movie { - title: String! - } -`; - -const neoSchema = new Neo4jGraphQL({ - typeDefs, - config: { - queryOptions: { - runtime: CypherRuntime.INTERPRETED, - }, - }, -}); - -const server = new ApolloServer({ - schema: await neoSchema.getSchema(), -}); - -await startStandaloneServer(server, { - context: async ({ req }) => ({ req }), -}); ----- - -a| -[source, javascript, indent=0] ----- -const { Neo4jGraphQL } = require("@neo4j/graphql"); -const { ApolloServer } = require("apollo-server"); - -const typeDefs = ` - type Movie { - title: String! - } -`; - -const neoSchema = new Neo4jGraphQL({ - typeDefs, -}); - -const server = new ApolloServer({ - schema: await neoSchema.getSchema(), -}); - -await startStandaloneServer(server, { - context: async ({ req }) => ({ cypherQueryOptions: { runtime: "interpreted" }}), -}); ----- -|=== - -=== `skipValidateTypeDefs` - -The argument has been moved to the top-level of the constructor input and renamed `validate`, which defaults to `true`. -If you started using the `config.startupValidation` option, this has also been rolled into the same `validate` setting for simplicity. - -Likewise, the `resolvers` option is now just a warning, and `noDuplicateRelationshipFields` is now a mandatory check rolled into `validate`. - -Here is an example query of how it looks now: - -[cols="1,1"] -|=== -|Before | After - -a| -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - config: { - skipValidateTypeDefs: true, - }, -}) ----- -a| -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - validate: false, -}) ----- -|=== - -=== `@cypher` - -The default behavior of the `@cypher` directive regarding the translation has changed. -Instead of using https://neo4j.com/labs/apoc/4.0/overview/apoc.cypher/apoc.cypher.runFirstColumnMany/[apoc.cypher.runFirstColumnMany], it directly wraps the query within a `CALL { }` subquery. - -This update has proven to be more performant for the same queries, however, it may lead to unexpected changes, mainly when using Neo4j 5.x, where the subqueries need to be _aliased_. - -On top of that, to improve performance, it is recommended to pass the returned alias in the property `columnName`, to ensure the subquery is properly integrated into the larger query. - -For example, the GraphQL query: - -[source, graphql, indent=0] ----- -type query { - test: String! @cypher(statement: "RETURN 'hello'") -} ----- - -Would be translated to: -[source,cypher, indent=0] ----- -CALL { - RETURN 'hello' -} -WITH 'hello' AS this -RETURN this ----- - -Which is invalid in Neo4j 5.x. -To fix it, ensure the `RETURN` elements are aliased: - -[source, graphql, indent=0] ----- -type query { - test: String! @cypher(statement: "RETURN 'hello' as result") -} ----- - -Another way to use this update is through an experimental option with the `columnName` flag in the `@cypher` directive: - -[source, graphql, indent=0] ----- -type query { - test: String! @cypher(statement: "RETURN 'hello' as result", columnName: "result") -} ----- - -Note that escaping strings are no longer needed in Neo4j GraphQL 4.0.0. - -=== `@fulltext` - -In version 4.0.0, a number of improvements have been made to full-text queries. -These include the ability to return the full-text score, filter by the score and sorting by the score. -However, these improvements required a number of breaking changes. - -==== Full-text queries - -Full-text queries now need to be performed using a top-level query, instead of being performed using an argument on a node query. - -As a result, the following query is now invalid: - -[source, graphql, indent=0] ----- -query { - movies(fulltext: { movieTitleIndex: { phrase: "Some Title" } }) { - title - } -} ----- - -The new top-level queries can be used to return the full-text score, which indicates the confidence of a match, as well as the nodes that have been matched. -They now accept the following arguments: - -* `phrase`: specifies the string to search for in the full-text index. -* `where`: accepts a min/max score as well as the normal filters available on a node. -* `sort: used to sort using the score and node attributes. -* `limit`: used to limit the number of results to the given integer. -* `offset`: used to offset by the given number of results. - -This means that, for the following type definition: - -[source, graphql, indent=0] ----- -type Movie @fulltext(indexes: [{ indexName: "MovieTitle", fields: ["title"] }]) { # Note that indexName is the new name for the name argument. More about this below. - title: String! -} ----- - -The following top-level query and type definitions would be generated by the library: - -[source, graphql, indent=0] ----- -type Query { - movieFulltextMovieTitle(phrase: String!, where: MovieFulltextWhere, sort: [MovieFulltextSort!], limit: Int, offset: Int): [MovieFulltextResult!]! -} - -"""The result of a fulltext search on an index of Movie""" -type MovieFulltextResult { - score: Float - movies: Movie -} - -"""The input for filtering a fulltext query on an index of Movie""" -input MovieFulltextWhere { - score: FloatWhere - movie: MovieWhere -} - -"""The input for sorting a fulltext query on an index of Movie""" -input MovieFulltextSort { - score: SortDirection - movie: MovieSort -} - -"""The input for filtering the score of a fulltext search""" -input FloatWhere { - min: Float - max: Float -} ----- - -This query can then be used to perform a full-text query: - -[source, graphql, indent=0] ----- -query { - movieFulltextMovieTitle( - phrase: "Full Metal Jacket", - where: { score: min: 0.4 }, - sort: [{ movie: { title: ASC } }], - limit: 5, - offset: 10 - ) { - score - movies { - title - } - } -} ----- - -And thus return results in the following format: - -[source, json, indent=0] ----- -{ - "data": { - "movieFulltextMovieTitle": [ - { - "score": 0.44524085521698, - "movie": { - "title": "Full Moon High" - } - }, - { - "score": 1.411118507385254, - "movie": { - "title": "Full Metal Jacket" - } - } - ] - } -} ----- - -==== Argument changes - -The following changes have been made to `@fulltext` arguments: - -* `queryName` has been added to specify a custom name for the top-level query that is generated. -* `name` has been renamed to `indexName` to avoid ambiguity with the new `queryName` argument. - -These changes mean that the following type definition is now invalid: - -[source, graphql, indent=0] ----- -type Movie @fulltext(indexes: [{ name: "MovieTitle", fields: ["title"] }]) { - title: String! -} ----- - -The `name` argument now needs to be replaced with `indexName`: - -[source, graphql, indent=0] ----- -type Movie @fulltext(indexes: [{ indexName: "MovieTitle", fields: ["title"] }]) { - title: String! -} ----- - -As an example, the `queryName` argument can be used as: - -[source, graphql, indent=0] ----- -type Movie @fulltext(indexes: [{ queryName: "moviesByTitle", indexName: "MovieTitle", fields: ["title"] }]) { - title: String! -} ----- - -This means the top-level query is now `moviesByTitle` instead of `movieFulltextMovieTitle`: - -[source, graphql, indent=0] ----- -type Query { - moviesByTitle(phrase: String!, where: MovieFulltextWhere, sort: [MovieFulltextSort!], limit: Int, offset: Int): [MovieFulltextResult!]! -} ----- - -== Subscription options - -Subscriptions are no longer configured as a plugin, but as a feature within the `features` option. - -[cols="1,1"] -|=== -|Before | Now - -a| -[source, javascript] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - plugins: { - subscriptions: plugin, - }, -}); ----- -a| -[source, javascript] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - features: { - subscriptions: plugin, - }, -}); ----- -|=== - -=== Default subscriptions - -The class `Neo4jGraphQLSubscriptionsSingleInstancePlugin` is no longer exported. -Instead, the default subscriptions behavior can be enabled by setting the `subscriptions` option to `true` . - -[cols="1,1"] -|=== -|Before | Now - -a| -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - plugin: { - subscriptions: new Neo4jGraphQLSubscriptionsSingleInstancePlugin(), - }, -}); ----- -a| -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - features: { - subscriptions: true - }, -}); ----- -|=== - -=== Neo4j GraphQL subscriptions AMQP package - -The name of the interface underlying the subscriptions system has changed from `Neo4jGraphQLSubscriptionsPlugin` to `Neo4jGraphQLSubscriptionsEngine`. -If you were previously using the `@neo4j/graphql-plugins-subscriptions-amqp` package, this has been changed to `@neo4j/graphql-amqp-subscriptions-engine` to reflect this underlying change. - -To keep using it, uninstall the previous package and install the new one: - -[source, bash, indent=0] ----- -npm uninstall @neo4j/graphql-plugins-subscriptions-amqp -npm install @neo4j/graphql-amqp-subscriptions-engine ----- - -Then update any imports: - -[cols="1,1"] -|=== -|From | To - -a| -[source, javascript, indent=0] ----- -import { Neo4jGraphQLSubscriptionsAMQPPlugin } from "@neo4j/graphql-plugins-subscriptions-amqp"; ----- -a| -[source, javascript, indent=0] ----- -import { Neo4jGraphQLAMQPSubscriptionsEngine } from "@neo4j/graphql-amqp-subscriptions-engine"; ----- -|=== - -And change the instantiations: - -[cols="1,1"] -|=== -|From | To - -a| -[source, javascript, indent=0] ----- -const plugin = new Neo4jGraphQLSubscriptionsAMQPPlugin({ - connection: { - hostname: "localhost", - username: "guest", - password: "guest", - }, -}); ----- -a| -[source, javascript, indent=0] ----- -const subscriptionsEngine = new Neo4jGraphQLAMQPSubscriptionsEngine({ - connection: { - hostname: "localhost", - username: "guest", - password: "guest", - }, -}); ----- -|=== - -=== Custom subscription plugins - -The underlying subscription system has not changed. -Custom behavior can be implemented the same way, by creating a class implementing the interface described in xref::subscriptions/engines.adoc#custom-subscription[Subscriptions engines]. - -However, if using TypeScript, the exported interface to implement these classes has been renamed from `Neo4jGraphQLSubscriptionsPlugin` to `Neo4jGraphQLSubscriptionsEngine`. - -== Updated directives - -A number of directives and their arguments have been renamed in order to make using `@neo4j/graphql` more intuitive. -Here is a table with all the changes: - -[cols="1,2,2"] -|=== -|Before | Now | Example - -|`@alias` -|Properties in the alias directive are now automatically escaped using backticks. -If you were using backticks in the `property` argument of your `@alias` directives, you should now remove the escape strings as this is covered by the library. -a| -[source, graphql, indent=0] ----- -type User { - id: ID! @id - username: String! @alias(property: "dbUserName") -} ----- - -|`@callback` -|Renamed to `@populatedBy`. -Additionally, the `name` argument has been renamed to `callback` and it is still used to specify the callback used to populate the field's value. -a| -.Before -[source, graphql, indent=0] ----- -type User { - id: ID! @callback(name: "nanoid", operations: [CREATE]) - firstName: String! - surname: String! -} ----- - -.Now -[source, graphql, indent=0] ----- -new Neo4jGraphQL({ - typeDefs, - features: { // changed from config - populatedBy: { // changed from callback - callbacks: { - nanoid: () => { return nanoid(); } - } - } - } -}); ----- - -|`@computed` -a|Renamed to `@customResolver`. -Note that before and after these changes, a custom resolver needs to be defined as follows: - -[source, javascript, indent=0] ----- -new Neo4jGraphQL({ - typeDefs, - resolvers: { - User: { - fullName: ({ firstName, lastName }, args, context, info) => (`${firstName} ${lastName}`), - } - } -}); ----- - -a| -.Before -[source, graphql, indent=0] ----- -type User { - firstName: String! - lastName: String! - fullName: String! @computed(from: ["firstName", "lastName"]) -} ----- - -.Now -[source, graphql, indent=0] ----- -type User { - firstName: String! - lastName: String! - fullName: String! @customResolver(requires: ["firstName", "lastName"]) -} ----- - -|`from` -a| Renamed to `requires`. -In version 4.0.0, it is now possible to require non-scalar fields, which means it is also possible to require fields on related type. + -{nbsp} + -Additionally, the `requires` argument now accepts a GraphQL selection set instead of a list of strings and also validates the required selection set against your type definitions. -This means that if there is no field called `someFieldThatDoesNotExist`, an error would be thrown on startup if you tried to use the following type definitions: + -{nbsp} + -[source, graphql, indent=0] ----- -type User { - firstName: String! - lastName: String! - fullName: String! @customResolver(requires: "firstName someFieldThatDoesNotExist") -} ----- - -a| -.Before -[source, graphql, indent=0] ----- -type User { - firstName: String! - lastName: String! - fullName: String! @customResolver(requires: ["firstName", "lastName"]) -} ----- - -.Now -[source, graphql, indent=0] ----- -type User { - firstName: String! - lastName: String! - fullName: String! @customResolver(requires: "firstName lastName") -} ----- - -.Additional example -[source, graphql, indent=0] ----- -interface Publication { - publicationYear: Int! -} - -type Author { - name: String! - publications: [Publication!]! @relationship(type: "WROTE", direction: OUT) - publicationsWithAuthor: [String!]! - @customResolver( - requires: "name publications { publicationYear ...on Book { title } ... on Journal { subject } }" - ) -} - -type Book implements Publication { - title: String! - publicationYear: Int! - author: [Author!]! @relationship(type: "WROTE", direction: IN) -} - -type Journal implements Publication { - subject: String! - publicationYear: Int! - author: [Author!]! @relationship(type: "WROTE", direction: IN) -} ----- - -|`@exclude` -|Replaced by `@query`, `@mutation`, and `@subscription`. -These new directives allow for fully granular configuration for each operation. -a|* `@exclude` -> `@query(read: false, aggregate: false) @mutation(operations: []) @subscription(events: [])`. + -* `@exclude(operations: [READ])` -> `@query(read: false, aggregate: false)`. + -* `@exclude(operation: [CREATE, UPDATE, DELETE])` -> `@mutation(operations: [])`. - -|`@id` -a|Deprecated with _all_ of its arguments removed and/or replaced. -a|*`autogenerate` -> `@unique`* + -The default value was `true`. -If set to `false`, the `@id` directive was almost a no-op only used to manage a unique node property constraint. -Use the `@unique` directive instead. + -{nbsp} + -*`global` -> `@relayId`* + -This argument was used to configure the field that would form the global node identifier for Relay. -This functionality has been moved into its own directive, `@relayId`. The use of `@relayId` will ensure a unique node property constraint for the field. + -{nbsp} + -*`@id` -> `unique` + `@id`* + -The `@id` directive used to also manage unique node property constraints for a field. -This functionality has been removed. -Use the `@unique` directive in combination with `@id` if you want the field to be backed by a constraint. - -|`@plural` -|Removed from `@node` and replaced by the `@plural` directive. -It takes the pluralized type name using the `value` argument. -a| -.Invalid `plural` type definition -[source, graphql, indent=0] ----- -type Tech @node(label: "TechDB", plural: "Techs") { - name: String -} ----- - -.Updated version - -[source, graphql, indent=0] ----- -type Tech @node(label: "TechDB") @plural(value: "Techs") { - name: String -} ----- - -|`label` and `additionalLabels` -a|Removed from `@node` and replaced by `labels`. -It accepts a list of string labels that are used when a node of the given GraphQL type is created. + -{nbsp} + -Note that defining `labels` means taking control of the database labels of the node. -Indexes and constraints in Neo4j only support a single label, for which the first element of the `labels` argument will be used. + -{nbsp} + -As before, providing none of these arguments results in the node label being the same as the GraphQL type name. -This can cause implications on constraits. -For instance, in the case where unique constraint is asserted for the label `Tech` and the property `name`: + -{nbsp} + -[source, graphql, indent=0] ----- -type Tech @node(labels: ["Tech", "TechDB"]) { - name: String @unique -} ----- -a| -.Current equivalent to `label` -[source, graphql, indent=0] ----- -type Tech @node(label: "TechDB") { - name: String -} -# becomes -type Tech @node(labels: ["TechDB"]) { - name: String -} ----- - -.Current equivalent to `additionalLabels` -[source, graphql, indent=0] ----- -type Tech @node(additionalLabels: ["TechDB"]) { - name: String -} -# becomes -type Tech @node(labels: ["Tech", "TechDB"]) { - name: String -} ----- - -.Current equivalent to both arguments -[source, graphql, indent=0] ----- -type Tech @node(label: "TechDB", additionalLabels: ["AwesomeTech"]) { - name: String -} -# becomes -type Tech @node(labels: ["TechDB", "AwesomeTech"]) { - name: String -} ----- - -|`@queryOptions` and `limit` -| Removed and moved to `@limit`. -a| -.Outdated example -[source, graphql, indent=0] ----- -type Record @queryOptions(limit: { default: 10, max: 100 }) { - id: ID! -} ----- - -.Updated version using `@limit` -[source, graphql, indent=0] ----- -type Record @limit(default: 10, max: 100) { - id: ID! -} ----- - -|`@readonly` and `@writeonly` -|Removed and replaced by the `@selectable` and `@settable` directives. -They can be used to configure not only if fields are readable or writable, but also when they should be readable or writable. -a|* `@readonly` -> `@settable(onCreate: false, onUpdate: false)` -* `@writeonly` -> `@selectable(onRead: false, onAggregate: false)` - -|`@query` and `@relationship` -|Aggregation operations are no longer generated by default. -They can be enabled case by case using the directives xref::/schema-configuration/type-configuration.adoc#_query[`@query`] and xref::/schema-configuration/field-configuration.adoc#_relationship[`@relationship`]. -a| -[source, graphql, indent=0] ----- -type Movie { - title: String! -} - -type Actor @query(aggregate: true) { - name: String! - actedIn: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, aggregate: true) -} ----- -|=== - -[relationship-aggregate] -== Relationship updates - -Here are the changes and updates to `@relationship`-related features. - -[discrete] -=== *Relationship types are now automatically escaped* - -Relationship types are now automatically escaped. -If you have previously escaped your relationship types using backticks, you must now remove these as this is covered by the library. - -[discrete] -=== *`@relationshipProperties` now mandatory* - -Current changes require the distinction between interfaces that are used to specify relationship properties, and others. -Therefore, the `@relationshipProperties` directive is now required on all relationship property interfaces. -If it is not included, an error is thrown. - -As a result, in version 4.0.0, the following type definitions are invalid: - -[source, graphql, indent=0] ----- -type Person { - name: String! - movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedIn") -} - -type Movie { - title: String! - actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn") -} - -interface ActedIn { - screenTime: Int! -} ----- - -`ActedIn` must be decorated with `@relationshipProperties`: - -[source, graphql, indent=0] ----- -interface ActedIn @relationshipProperties { - screenTime: Int! -} ----- - -[discrete] -=== Duplicate relationship fields are now checked for - -In 3.0.0, it was possible to define schemas with types that have multiple relationship fields connected by the same type of relationships. -Now, this kind of scenario is detected during schema generation and an error is thrown so developers are informed to fix the type definitions. - -Here is an example of what is now considered invalid with these checks: - -[source, graphql, indent=0] ----- -type Team { - player1: Person! @relationship(type: "PLAYS_IN", direction: IN) - player2: Person! @relationship(type: "PLAYS_IN", direction: IN) - backupPlayers: [Person!]! @relationship(type: "PLAYS_IN", direction: IN) -} - -type Person { - teams: [Team!]! @relationship(type: "PLAYS_IN", direction: OUT) -} ----- - -In this example, there are multiple fields in the `Team` type which have the same `Person` type, the same `@relationship` type and ("PLAYS_IN") direction (IN). This is an issue when returning data from the database, as there would be no difference between `player1`, `player2` and `backupPlayers`. Selecting these fields would then return the same data. - -These checks can be disabled by disabling all validation in the library, however, this is not recommended unless in production with 100% confidence of type definitions input. - -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - validate: false, -}); ----- - -== `cypherParams` - -In 3.0.0, `cypherParams` was available in the context to provide the ability to pass arbitrary parameters to a custom Cypher query. -This functionality remains in 4.0.0, but you no longer have to use the `$cypherParams` prefix to reference these parameters. diff --git a/modules/ROOT/pages/migration/4.0.0/ogm.adoc b/modules/ROOT/pages/migration/4.0.0/ogm.adoc deleted file mode 100644 index 4257037a..00000000 --- a/modules/ROOT/pages/migration/4.0.0/ogm.adoc +++ /dev/null @@ -1,50 +0,0 @@ -= OGM -:description: This page describes what updates were made to the OGM tool in version 4.0.0 of the Neo4j GraphQL Library. -:page-aliases: guides/v4-migration/ogm.adoc, migration/v4-migration/ogm.adoc - -This page describes what updates were made to the OGM tool in version 4.0.0 of the Neo4j GraphQL Library. - -== Database specification - -The method to specify the database that the OGM should use has been changed. -This was previously configured using the `driverConfig` option: - -[source, javascript, indent=0] ----- -import { OGM } from "@neo4j/graphql-ogm"; -import neo4j from "neo4j-driver"; - -const typeDefs = `#graphql - type User { - name: String - } -`; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("username", "password") -); - -const ogm = new OGM({ typeDefs, driver, driverConfig: { database: "some-other-database" } }); ----- - -And now it has been raised to the top-level: - -[source, javascript, indent=0] ----- -import { OGM } from "@neo4j/graphql-ogm"; -import neo4j from "neo4j-driver"; - -const typeDefs = `#graphql - type User { - name: String - } -`; - -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("username", "password") -); - -const ogm = new OGM({ typeDefs, driver, database: "some-other-database" }); ----- From eab3e8a58dc89b81c0d967cf4d77f0bd4d5108d4 Mon Sep 17 00:00:00 2001 From: lidiazuin Date: Tue, 5 Mar 2024 11:54:00 +0100 Subject: [PATCH 20/23] update --- .github/workflows/docs-pr.yml | 5 +- .github/workflows/docs-teardown.yml | 3 + .../ROOT/pages/subscriptions/filtering.adoc | 804 +++--------------- .../type-definitions/types/interfaces.adoc | 2 +- .../type-definitions/types/relationships.adoc | 2 +- package-lock.json | 196 ++--- package.json | 10 +- yarn.lock | 2 +- 8 files changed, 248 insertions(+), 776 deletions(-) diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yml index db2d036b..deb198ae 100644 --- a/.github/workflows/docs-pr.yml +++ b/.github/workflows/docs-pr.yml @@ -6,7 +6,10 @@ name: "Verify PR" on: pull_request: - + branches: + - "main" + - "dev" + jobs: # note that the build job requires a build-verify script in package.json diff --git a/.github/workflows/docs-teardown.yml b/.github/workflows/docs-teardown.yml index ddaea218..38380bc3 100644 --- a/.github/workflows/docs-teardown.yml +++ b/.github/workflows/docs-teardown.yml @@ -3,6 +3,9 @@ name: "Documentation Teardown" on: pull_request_target: + branches: + - "main" + - "dev" types: - closed diff --git a/modules/ROOT/pages/subscriptions/filtering.adoc b/modules/ROOT/pages/subscriptions/filtering.adoc index da5592f0..17289464 100644 --- a/modules/ROOT/pages/subscriptions/filtering.adoc +++ b/modules/ROOT/pages/subscriptions/filtering.adoc @@ -1,754 +1,220 @@ -[[filtering]] -:description: This page covers how to apply filters to subscriptions in the Neo4j GraphQL Library. -= Filtering +[[troubleshooting]] += Troubleshooting +:page-aliases: troubleshooting/faqs.adoc, troubleshooting/security.adoc, troubleshooting/optimizing-create-operations.adoc, appendix/preventing-overfetching.adoc, appendix.adoc -This page covers how to apply filters to subscriptions in the Neo4j GraphQL Library. -Note, however, that: -* Filtering can only be applied at the root of the subscription operation. -* Aggregations are not supported on subscription types and there is currently no available method. +This chapter contains common troubleshooting steps. Additionally, there is a section for xref::troubleshooting.adoc#troubleshooting-faqs[FAQs] (Frequently Asked Questions) where you might find answers to your problems. -A subscription can be created to target the changes to a node (`create`/`update`/`delete``) or to a relationship (`create`/`delete``). +[[troubleshooting-debug-logging]] +== Debug Logging -While the format slightly differs depending on whether the subscription targets a node or a relationship, providing a `where` argument allows for filtering on the events that are returned to the subscription. +=== For `@neo4j/graphql` -== Operators +`@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:*` when running. For example: -When creating a subscription, a number of operators are available for different types in the `where` argument. +==== Command line -=== Equality operators - -All types can be tested for either equality or non-equality. -For the `Boolean` type, these are the only available comparison operators. - -[[filtering-numerical-operators]] -=== Numerical operators - -The following comparison operators are available for numeric types (`Int`, `Float`, xref::type-definitions/types/index.adoc[`BigInt`]): - -* `_LT` -* `_LTE` -* `_GTE` -* `_GT` - -[NOTE] -==== -Filtering on xref::type-definitions/types/temporal.adoc[Temporal types] and xref::type-definitions/types/spatial.adoc[Spatial types] is not currently supported. -==== - -=== String comparison - -The following case-sensitive comparison operators are only available for use on `String` and `ID` types: - -* `_STARTS_WITH` -* `_ENDS_WITH` -* `_CONTAINS` - -=== Array comparison - -The following operator is available on non-array fields, and accepts an array argument: - -* `_IN` - -Conversely, the following operator is available on array fields, and accepts a single argument: - -* `_INCLUDES` - -These operators are available for all types apart from `Boolean`. - -=== `AND`/`OR` operators - -Complex combinations of operators are possible using the `AND`/ `OR` operators. -They accept as argument an array of items of the same format as the `where` argument. - -Check xref:subscriptions/filtering.adoc#combining-operators[Combining operators] for an example. - -[[node-events-usage]] -== Subscribing to node events - -The `where` argument allows specifying filters on top-level properties of the targeted nodes. -Only events matching these properties and type are returned to the subscription. - -As an example, consider the following type definitions: - -[source, graphql, indent=0] ----- -type Movie { - title: String - genre: String - averageRating: Float - releasedIn: Int -} ----- - -Now, here is how filtering can be applied when creating a subscription: - -=== `CREATE` - -To filter subscriptions to `create` operations for movies by their genre, here is how to do it: - -[source, graphql, indent=0] +[source, bash, indent=0] ---- -subscription { - movieCreated(where: {genre: "Drama"}) { - createdMovie { - title - } - } -} +DEBUG=@neo4j/graphql:* node src/index.js ---- -This way, only newly created movies with the genre `"Drama"` trigger events to this subscription. - -[NOTE] -==== -The `where` argument only filters by properties set at the moment of creation. -==== - -=== `UPDATE` - -Here is how to filter subscription to `update` operations in movies with average rating bigger than 8: - -[source, graphql, indent=0] ----- -subscription { - movieUpdate(where: {averageRating_GT: 8}) { - updatedMovie { - title - } - } -} ----- +Alternatively, if you are debugging a particular functionality, you can specify a number of namespaces to isolate certain log lines: -This way, you should only receive events triggered by movies with the average rating bigger than 8 when they are modified. +1. `@neo4j/graphql:*` - Logs all +2. `@neo4j/graphql:auth` - Logs the status of authorization header and token extraction, and decoding of JWT +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 -[NOTE] -==== -The `where` argument only filters properties that already existed *before* the update. -==== +==== Constructor -This is how these events may look like: +You can also enable all debug logging in the library by setting the `debug` argument to `true` in the constructor. -[source, graphql, indent=0] +[source, javascript, indent=0] ---- -mutation { - makeTheMatrix: createMovies(input: {title: "The Matrix", averageRating: 8.7}) { - title - averageRating - }, - makeResurrections: createMovies(input: {title: "The Matrix Resurrections", averageRating: 5.7}) { - title - averageRating - }, -} +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const neo4j = require("neo4j-driver"); +const { ApolloServer } = require("apollo-server"); -mutation { - updateTheMatrix: updateMovie( - where: {title: "The Matrix"} - update: {averageRating: 7.9} - ) { - title - }, - updateResurrections: updateMovie( - where: {title: "The Matrix Resurrections"} - update: {averageRating: 8.9} - ) { - title +const typeDefs = ` + type Movie { + title: String! } -} ----- - -Therefore, given the previously described subscription, these GraphQL operations should only triggered for `"The Matrix"` movie. +`; -=== `DELETE` +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("username", "password") +); -Here is how to filter subscription to `delete` operations in movies by their genre, using the `NOT` filter: - -[source, graphql, indent=0] +const neoSchema = new Neo4jGraphQL({ + typeDefs, + driver, + debug: true, +}); ---- -subscription { - movieDeleted(where: { NOT: { genre: "Comedy" } }) { - deletedMovie { - title - } - } -} ----- - -This way, only deleted movies of all genres except for `"Comedy"` should trigger events to this subscription. - -[NOTE] -==== -The `where` argument only filters properties that already existed before the deletion process. -==== -[[combining-operators]] -=== Combining operators +=== For `@neo4j/introspector` -All previously mentioned operators can be combined using the `AND`, `OR`, and `NOT` operators. -They accept an array argument with items of the same format as the `where` argument, which means they can also be nested to form complex combinations. +`@neo4j/introspector` has its own debug logging namespace and you can turn on logging for it with: -As an example, consider a user who likes comedy movies, but not romantic comedies from early 2000, and who has the Matrix Trilogy as their favorite titles. -They could subscribe to any updates that cover this particular set of interests as follows: - -[source, graphql, indent=0] +[source, bash, indent=0] ---- -subscription { - movieUpdated(where: { - OR: [ - { title_CONTAINS: "Matrix" }, - { genre: "comedy" }, - { AND: [ - { NOT: { genre: "romantic comedy" } }, - { releasedIn_GT: 2000 }, - { releasedIn_LTE: 2005 } - ] }, - ] - }) { - updatedMovie { - title - } - } -} +DEBUG=@neo4j/introspector node src/index.js ---- +Read more about the xref::introspector.adoc[introspector]. -== Subscribing to relationship events - -When subscribing to relationship events, the `where` argument still allows specifying filters on the top-level properties of the targeted nodes. -It also supports specifying filters on the relationship properties (`edge`) and on the top-level properties (`node`) of the nodes at the other end of the relationship. -This is done by using the operators previously described, and the usage is very similar to xref:subscriptions/filtering.adoc#node-events-usage[subscribing to node events]. - -However, filtering by relationship events is an even more powerful logic. -This is because these filters can also express the expected relationship field, or the expected concrete type at the other end of the relationship, provided that they are connected to abstract types. +[[troubleshooting-query-tuning]] +== Query Tuning -Note that each relationship field specified is combined with the others using a xref:subscriptions/filtering.adoc#filter-logical-or[logical `OR`]. -Only events matching these relationship field names are returned in the subscription. +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 in the request context. -You can further filter each relationship field by node and relationship properties. -These fields are combined in the resulting filter with a xref:subscriptions/filtering.adoc#filter-logical-and[logical `AND`]. +You can read more about the available query options at https://neo4j.com/docs/cypher-manual/current/query-tuning/query-options/#cypher-query-options[Cypher Manual -> Query Options]. -As an example, in the following type definitions: - -[source, graphql, indent=0] ----- -type Movie { - title: String - genre: String - actors: [Actor!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN) -} +_Please only set these options if you know what you are doing._ -type ActedIn @relationshipProperties { - screenTime: Int! -} +For example, to set the "runtime" option to "interpreted": -type Actor { - name: String -} +[source, javascript, indent=0] ---- +const { Neo4jGraphQL } = require("@neo4j/graphql"); +const neo4j = require("neo4j-driver"); +const { ApolloServer } = require("apollo-server"); -The format of the `where` argument is: - -[source, graphql, indent=0] ----- -{ - movie: { - # top-level properties of the node targeted for the subscription operation, supports operators - title_IN: ["The Matrix", "Fight Club"] - }, - createdRelationship: { - actors: { # field name corresponding to a relationship in the type definition of the node targeted for the subscription operation - edge: { - # properties of the relationship, supports operators - screenTime_GT: 10, - }, - node: { - # top-level properties of the node on the other end of the relationship, supports operators - name_STARTS_WITH: "Brad" - } - } +const typeDefs = ` + type Movie { + title: String! } -} ----- - -The following sections feature examples of how filtering can be applied when creating a subscription to relationship events. +`; -=== Newly created relationship +const driver = neo4j.driver( + "bolt://localhost:7687", + neo4j.auth.basic("username", "password") +); -The following example filters the subscriptions to newly created relationships that are connecting a `Movie` from genres other than "Drama", and to an `Actor` with a screen time bigger than 10 minutes: +const neoSchema = new Neo4jGraphQL({ + typeDefs, + driver, +}); -[source, graphql, indent=0] ----- -subscription { - movieRelationshipCreated(where: { movie: { NOT: { genre: "Drama" } }, createdRelationship: { actors: { edge: { screenTime_GT: 10 } } } }) { - movie { - title - } - createdRelationship { - actors { - screenTime - node { - name - } - } - } - } -} ----- - -[NOTE] -==== -The `where` argument only filters already existing properties at the moment of the relationship creation. -==== - -=== Newly deleted relationship - -The following example filters the subscriptions to deleted relationships that were connecting a `Movie` of the genre `"Comedy"` or `"Adventure"` to an `Actor` named `"Jim Carrey"`: - -[source, graphql, indent=0] ----- -subscription { - movieRelationshipDeleted(where: { movie: { genre_IN: ["Comedy", "Adventure"] }, createdRelationship: { actors: { node: { name: "Jim Carrey" } } } }) { - movie { - title - } - deletedRelationship { - actors { - screenTime - node { - name - } - } - } - } -} ----- - -[NOTE] -==== -The `where` argument only filters properties that already existed before the relationship deletion. -==== - -=== Relationship-related filters - -In addition to filtering node or relationship properties, the relationship-related filtering logic is even more powerful. -This is because these filters can also express the expected relationship field, or the expected concrete type at the other end of the relationship, provided that they are connected to abstract types. - -The following examples are valid for both `CREATE_RELATIONSHIP` and `DELETE_RELATIONSHIP` events. -Their purpose is to illustrate the various ways in which a subscription to a relationship event can be filtered. - -Considering the following type definitions: - -[source, graphql, indent=0] ----- -type Movie { - title: String - genre: String - actors: [Actor!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN) - directors: [Director!]! @relationship(type: "DIRECTED", properties: "Directed", direction: IN) - reviewers: [Reviewer!]! @relationship(type: "REVIEWED", properties: "Review", direction: IN) -} - -type ActedIn @relationshipProperties { - screenTime: Int! -} - -type Actor { - name: String -} - -type Person implements Reviewer { - name: String - reputation: Int -} - -union Director = Person | Actor - -type Directed @relationshipProperties { - year: Int! -} - -interface Reviewer { - reputation: Int! -} - -type Magazine implements Reviewer { - title: String - reputation: Int! -} - -type Review @relationshipProperties { - score: Int! -} ----- - -And the base subscription operation: - -[source, graphql, indent=0] ----- -subscription MovieRelationshipDeleted($where: MovieRelationshipDeletedSubscriptionWhere) { - movieRelationshipDeleted(where: $where) { - movie { - title - } - deletedRelationship { - actors { - screenTime - node { - name - } - } - directors { - year - node { - ... on PersonEventPayload { # generated type - name - reputation - } - ... on ActorEventPayload { # generated type - name - } - } - } - reviewers { - score - node { - reputation - ... on MagazineEventPayload { # generated type - title - reputation - } - ... on PersonEventPayload { # generated type - name - reputation - } - } - } - } - } -} ----- - -You can use the following `where` inputs in the GraphQL variable values to get different results: - -==== Filtering via implicit/explicit declaration - -Implicit or explicit declaration is used to filter specific relationship types that are expected to be returned to a subscription. - -For example, when subscribing to created or deleted relationships to a `Movie`, a user might only be interested in the relationship of type `ACTED_IN`, but indifferent to the properties of the `Actor` node or the other relationships connected to it. -In this case, the corresponding field name of this relationship is `actors`. - -By explicitly specifying the `actors` field name, you can filter-out events to other relationship properties: - -[source, graphql, indent=0] ----- -{ - where: { - deletedRelationship: { - actors: {} # no properties specified here, therefore all relationships to this field name will be returned - } - } -} ----- - -In case you are interested in `Actor` nodes conforming to some filters, for example with the name starting with the letter "A", the procedure is no different than xref:subscriptions/filtering.adoc#node-events-usage[subscribing to node events]: - -[source, graphql, indent=0] ----- -{ - where: { - deletedRelationship: { - actors: { - node: { # use operations to specify filers on the top-level properties of the node at the other end of the relationship - name_STARTS_WITH: "A" - } - } - } - } -} ----- - -If you are also interested in the relationship itself conforming to some filters, such as the `Actor` having spent no more than 40 minutes in the `Movie`, this is how the query may look: - -[source, graphql, indent=0] ----- -{ - where: { - deletedRelationship: { - actors: { - edge: { # use operations to specify filers on the top-level properties of the relationship - screenTime_LT: 40, - } - node: { - name: "Alvin" - } - } - } - } -} ----- - -Multiple relationship types can also be included in the returned subscriptions by explicitly specifying the corresponding field names. -For instance: - -[source, graphql, indent=0] ----- -{ - where: { - deletedRelationship: { - actors: {}, # include all relationships corresponding of type `ACTED_IN` - directors: {} # include all relationships corresponding of type `DIRECTED` - # exclude relationships of type `REVIEWED` - } - } -} ----- - -Now, if you are interested in all relationship types, you can either express this implicitly by not specifying any: - -[source, graphql, indent=0] ----- -{ - where: { - deletedRelationship: {} # include all relationships of all types - } -} ----- - -Or explicitly by specifying the field names of all the relationships connected to the type targeted for the subscription: +neoSchema.getSchema().then((schema) => { + const server = new ApolloServer({ + schema, + context: ({ req }) => ({ + req, + cypherQueryOptions: { + runtime: "interpreted", + }, + }), + }); -[source, graphql, indent=0] ----- -{ - where: { - deletedRelationship: { - # include all relationships of all types - # subscription target type is `Movie`, which has the following relationship field names: - actors: {}, - directors: {}, - reviewers: {} - } - } -} + server.listen().then(({ url }) => { + console.log(`Server ready at ${url}`); + }); +}); ---- -Note, however, that as **any** filters are applied to **any** of the relationships, explicitly including those that you are interested in subscribing to is a **mandatory** step. +[[troubleshooting-faqs]] +== FAQs -For example, if all relationships should be returned, but you want to filter-out the `REVIEWED` ones which have a score lower than 7, this is how your query may look like: +This chapter contains commonly asked questions and their solutions. -[source, graphql, indent=0] ----- -{ - where: { - deletedRelationship: { - actors: {}, # include all relationships of type `ACTED_IN` - directors: {}, # include all relationships of type `DIRECTED` - reviewers: { # include all relationships of type `REVIEWED`, with the score property greater than 7 - edge: { - score_GT: 7 - } - } - } - } -} ----- +=== I've upgraded from <1.1.0 and my `DateTime` fields aren't sorting as expected -Different filters can also be applied to different relationships without any constraints. -For example: +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, graphql, indent=0] +[source, javascript, indent=0] ---- -{ - where: { - deletedRelationship: { - actors: { # include some relationships of type `ACTED_IN`, filtered by relationship property `screenTime` and node property `name` - edge: { - screenTime_LT: 60, - }, - node: { - name_IN: ["Tom Hardy", "George Clooney"] - } - }, - directors: {}, # include all relationships of type `DIRECTED` - reviewers: { # include some relationships of type `REVIEWED`, filtered by relationship property `score` only - edge: { - score_GT: 7 - } - } - } - } -} +MATCH (m:Movie) +WHERE apoc.meta.type(m.timestamp) = "STRING" +SET m.timestamp = datetime(m.timestamp) +RETURN m ---- -[[filter-logical-or]] - -[NOTE] -==== -In the previous example, there is an implicit logical `OR` between the `actors`, `directors`, and `reviewers` relationship fields. -This is to say that a relationship of **either** type `ACTED_IN` **or** of type `DIRECTED` **or** of type `REVIEWED` should trigger the previously described subscription. -==== +=== I've created some data and then gone to query it, but it's not there -[[filter-logical-and]] -[NOTE] -==== -There is an implicit logical `AND` between the `edge` and `node` fields inside of the `actors` relationship field. -In other words, the relationship of type `ACTED_IN` with the property `screenTime` less than 60 **and** a target node with name in `["Tom Hardy", "George Clooney"]` should trigger the subscription. -==== +If you use a causal cluster or an Aura Professional instance, there is a chance that the created data is not yet present on the server which gets connected to on the next GraphQL query. -=== Abstract types +You can ensure that the data is available to query by passing a bookmark into your request - see xref::driver-configuration.adoc#driver-configuration-bookmarks[Specifying Neo4j Bookmarks] for more information. -The following sections describe how to filter subscriptions using abstract types. +=== What is `_emptyInput` in my update and create inputs? -==== Union type +`_emptyInput` will appear in your update and create inputs if you define a type with only auto-generated and/or relationship properties. It is a placeholder property and therefore giving it a value in neither update nor create will give it a value on the node. `_emptyInput` will be removed if you add a user-provided property. -This example illustrates how to filter the node at the other end of the relationship when it is of a union type: +The following example will create inputs with `_emptyInput`: -[source, graphql, indent=0] +[source, graphql] ---- -{ - where: { - deletedRelationship: { - directors: { # relationship to a union type - Person: { # concrete type that makes up the union type - edge: { - year_GT: 2010 - }, - node: { - name: "John Doe", - reputation: 10 - } - }, - Actor: { # concrete type that makes up the union type - edge: { - year_LT: 2005 - }, - node: { - name: "Tom Hardy" - } - } - }, - } - } +type Cookie { + id: ID! @id + owner: Owner! @relationship(type: "HAS_OWNER", direction: OUT) + # f: String # If you don't want _emptyInput, uncomment this line. } ---- -The result is that only relationships of type `DIRECTED` are returned to the subscription, where the `Director` is a `Person` named `"John Doe"`, who directed the movie after 2010, **or** where the `Director` is an `Actor` named `"Tom Hardy"` who directed the movie before 2005. +=== Relationship nullability isn't being enforced in my graph -Note that the relationship field name is split into multiple sections, one for each of the concrete types that make up the union type. -The relationship properties do not exist outside the confines of one of these sections, even though the properties are the same. - -Now, take the other example that did not explicitly specify the concrete types: +Currently, and given the typeDefs below, Neo4j GraphQL will enforce cardinality when creating and updating a one-one relationship such as the movie director field below: [source, graphql, indent=0] ---- -{ - where: { - deletedRelationship: { - directors: {}, # include all relationships of type `DIRECTED` - } - } +type Movie { + title: String! + director: Person! @relationship(type: "DIRECTED", direction: IN) + actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN) } ----- -Following the same logic as for the relationship field names: when nothing is explicitly provided, then all is accepted. -Thus relationships of type `DIRECTED`, established between a `Movie` and any of the concrete types that make up the union type `Director` are returned to the subscription. - -It is equivalent to the following: - -[source, graphql, indent=0] ----- -{ - where: { - deletedRelationship: { - directors: { # include all relationships of type `DIRECTED` - Actor: {}, - Person: {} - } - } - } +type Person { + name: String! } ---- -Note that explicitly specifying a concrete type excludes the others from the returned events: +However, at this point, there is no mechanism to support validating the actors relationship. Furthermore, there is a known limitation given if you were create a movie and a director in one mutation: [source, graphql, indent=0] ---- -{ - where: { - deletedRelationship: { - directors: { - Actor: {} # include all relationships of type `DIRECTED` to an `Actor` type - } - } +mutation { + createMovies( + input: [ + { + title: "Forrest Gump" + director: { create: { node: { name: "Robert Zemeckis" } } } + } + ] + ) { + movies { + title + director { + name + } } + } } ---- -In this case, only relationships of type `DIRECTED` between a `Movie` and an `Actor` are returned to the subscription. -Those between a `Movie` and a `Person` are filtered out. - -One reason why this might be done is to include some filters on the `Actor` type: +Then delete the director node: [source, graphql, indent=0] ---- -{ - where: { - deletedRelationship: { - directors: { - Actor: { # include some relationships of type `DIRECTED` to an `Actor` type, that conform to the filters - node: { - NOT: { name: "Tom Hardy" } - } - } - } - } - } +mutation { + deletePeople(where: { name: "Robert Zemeckis" }) { + nodesDeleted + } } ---- -To include filters on the `Actor` type, but also include the `Person` type in the result, you need to make the intent explicit: - -[source, graphql, indent=0] ----- -{ - where: { - deletedRelationship: { - directors: { - Actor: { # include some relationships of type `DIRECTED` to an `Actor` type, that conform to the filters - node: { - NOT: { name: "Tom Hardy" } - } - }, - Person: {} # include all relationships of type `DIRECTED` to a `Person` type - } - } - } -} ----- +No error is thrown, even though the schema states that all movies must have a director thus technically rendering the movie node invalid. +Finally, we do not enforce relationship cardinality on union or interface relationships. -==== Interface type +[[security]] +== Security -The following example illustrates how to filter the node at the other end of the relationship when it is of an interface type: +This section describes security considerations and known issues. -[source, graphql, indent=0] ----- -{ - where: { - deletedRelationship: { - reviewers: { # relationship to an interface type - edge: { - # relationship properties of a relationship of type `REVIEWED` - score_GT: 7 - }, - node: { - # common fields declared by the interface - reputation_GTE: 8 - } - }, - } - } -} ----- +=== Authorization not triggered for empty match -This example returns events for relationships between the type `Movie` and `Reviewer`, where the score is higher than 7, and the `Reviewer` has a reputation greater or equal to 7. \ No newline at end of file +If a query yields no results, the xref::authentication-and-authorization/authorization.adoc[Authorization] process will not be triggered. +This means that the result will be empty, instead of throwing an authentication error. Unauthorized users may +then discern whether or not a certain type exists in the database, even if data itself cannot be accessed. \ No newline at end of file diff --git a/modules/ROOT/pages/type-definitions/types/interfaces.adoc b/modules/ROOT/pages/type-definitions/types/interfaces.adoc index a85af4cb..c9de4abf 100644 --- a/modules/ROOT/pages/type-definitions/types/interfaces.adoc +++ b/modules/ROOT/pages/type-definitions/types/interfaces.adoc @@ -198,4 +198,4 @@ query GetProductionsStartingWithThe { } } } ----- +---- \ No newline at end of file diff --git a/modules/ROOT/pages/type-definitions/types/relationships.adoc b/modules/ROOT/pages/type-definitions/types/relationships.adoc index 70b20ded..0f0fd7ac 100644 --- a/modules/ROOT/pages/type-definitions/types/relationships.adoc +++ b/modules/ROOT/pages/type-definitions/types/relationships.adoc @@ -324,4 +324,4 @@ type Post { ---- The relationship at `User.posts` is considered a "many" relationship, which means it should always be of type `NonNullListType` and `NonNullNamedType`. -In other words, both the array and the type inside of a "many" relationship should have a `!`. +In other words, both the array and the type inside of a "many" relationship should have a `!`. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6c2c328f..d542f8a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,9 @@ "version": "4.0.0", "license": "ISC", "dependencies": { - "@antora/cli": "^3.1.7", - "@antora/site-generator-default": "^3.1.7", - "@neo4j-antora/aliases-redirects": "^0.2.3", + "@antora/cli": "^3.1.5", + "@antora/site-generator-default": "^3.1.5", + "@neo4j-antora/aliases-redirects": "^0.2.2", "@neo4j-antora/antora-add-notes": "^0.3.1", "@neo4j-antora/antora-modify-sitemaps": "^0.4.4", "@neo4j-antora/antora-page-list": "^0.1.1", @@ -22,15 +22,15 @@ }, "devDependencies": { "express": "^4.18.2", - "nodemon": "^3.1.0" + "nodemon": "^3.0.2" } }, "node_modules/@antora/asciidoc-loader": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@antora/asciidoc-loader/-/asciidoc-loader-3.1.7.tgz", - "integrity": "sha512-sM/poPtAi8Cx0g2oLGHoEYjTdE9pvIYLgbHW07fGf6c9wQYMd2DMsevtbhNKWp+xqxj/QinToz4JOaNLoy1nfg==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@antora/asciidoc-loader/-/asciidoc-loader-3.1.6.tgz", + "integrity": "sha512-Tqy4QFuZiKe/yX+3H8+vTLE6HH+VDm9OkKwq3G769jcC+40wz6y3poV4r4t1XJFAWwa/AKGM99ZcnJcJ3rtW+A==", "dependencies": { - "@antora/logger": "3.1.7", + "@antora/logger": "3.1.6", "@antora/user-require-helper": "~2.0", "@asciidoctor/core": "~2.2" }, @@ -39,12 +39,12 @@ } }, "node_modules/@antora/cli": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@antora/cli/-/cli-3.1.7.tgz", - "integrity": "sha512-yHo30VmiLLsZU4JW8VZR6fql9m5lIxocA2d0tduKQ+r4YSD1kt+bbwX3You3iMwW7oLoNo62zfU76F8CQBnm2g==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@antora/cli/-/cli-3.1.6.tgz", + "integrity": "sha512-aJLN6JyXYUfMfMVr/6JEenxnnG8evKfyp01wd/Cj7mkJXD/lj/Gqws2bXyP/hjUEl/HuX4/P9AcIfMLT/vfQJw==", "dependencies": { - "@antora/logger": "3.1.7", - "@antora/playbook-builder": "3.1.7", + "@antora/logger": "3.1.6", + "@antora/playbook-builder": "3.1.6", "@antora/user-require-helper": "~2.0", "commander": "~10.0" }, @@ -56,18 +56,18 @@ } }, "node_modules/@antora/content-aggregator": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@antora/content-aggregator/-/content-aggregator-3.1.7.tgz", - "integrity": "sha512-2gBbxaDxqY4QRw9Vut0IbKNqI9zAAohxjT0zcA5Am10kE3ywvzXaBa3tvb+A7vUl/vRcCC0LPM7pO37RVrbsGA==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@antora/content-aggregator/-/content-aggregator-3.1.6.tgz", + "integrity": "sha512-kOKWn/1gBvd9XOp00/wFzH4lb3yCa5u65ZKmfe9VwC7uOl5Tg9zT0lxMa7miEbPAmfhcOr0zRYXa2ybsoKBWNw==", "dependencies": { "@antora/expand-path-helper": "~2.0", - "@antora/logger": "3.1.7", + "@antora/logger": "3.1.6", "@antora/user-require-helper": "~2.0", "braces": "~3.0", "cache-directory": "~2.0", "glob-stream": "~7.0", "hpagent": "~1.2", - "isomorphic-git": "~1.25", + "isomorphic-git": "~1.21", "js-yaml": "~4.1", "multi-progress": "~4.0", "picomatch": "~2.3", @@ -81,12 +81,12 @@ } }, "node_modules/@antora/content-classifier": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@antora/content-classifier/-/content-classifier-3.1.7.tgz", - "integrity": "sha512-94XwJ35pkWJU6dJqQg5oreJRCkaXwU6yw9XSyB731o4i/0romkazKnu7deHugqdgvzqn92AlMRG6R0clhJLEsw==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@antora/content-classifier/-/content-classifier-3.1.6.tgz", + "integrity": "sha512-e5Fs38Cfbl2kecxpRLFftflphbjg2wPfWlwjLZjs8d0R5ISSCM38q8ecDKCQHQlrYJkSrxiSpWqg0irNqAHnLw==", "dependencies": { - "@antora/asciidoc-loader": "3.1.7", - "@antora/logger": "3.1.7", + "@antora/asciidoc-loader": "3.1.6", + "@antora/logger": "3.1.6", "mime-types": "~2.1", "vinyl": "~2.2" }, @@ -95,11 +95,11 @@ } }, "node_modules/@antora/document-converter": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@antora/document-converter/-/document-converter-3.1.7.tgz", - "integrity": "sha512-cRVJf7QyclxjWbA0gWz7hncZYThIREikkwaxaa26LsRCfBNlw7wxs7lWejvIvEl1LVshupbinJwKUPPQPOsHhw==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@antora/document-converter/-/document-converter-3.1.6.tgz", + "integrity": "sha512-bdzlkwq1WMnfdc6FUZNcO58LwjMqYmv3m9dI/eAJryGiKa9ChBFskwA1ob7rB87Qxjzu6UHcNucbt910hjEOAw==", "dependencies": { - "@antora/asciidoc-loader": "3.1.7" + "@antora/asciidoc-loader": "3.1.6" }, "engines": { "node": ">=16.0.0" @@ -114,9 +114,9 @@ } }, "node_modules/@antora/file-publisher": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@antora/file-publisher/-/file-publisher-3.1.7.tgz", - "integrity": "sha512-UH2o0DJuv9BJvWgn+QSE3MhtHB3oN3lG5lJkV3fQi1jAV+qPIHoiTIYhbw02mj5KQ3Qbt7YWWAKZKuGl3rEdjg==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@antora/file-publisher/-/file-publisher-3.1.6.tgz", + "integrity": "sha512-UPTyFWY7lrG/Qj6SBxgoVBg1fW3JemJzW62y6pKuGHF59TEKJiaVTUlHEaVgkbpkCngn2os+VOX7fHK0jsBU9A==", "dependencies": { "@antora/expand-path-helper": "~2.0", "@antora/user-require-helper": "~2.0", @@ -129,9 +129,9 @@ } }, "node_modules/@antora/logger": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@antora/logger/-/logger-3.1.7.tgz", - "integrity": "sha512-Z2tfNIi9G4BnAZq26Kp30974FxCVCtvH46FOi6ClnkJg6Uf2gTrVlJERmtsDTsHjWsf1qKbnj/4b99/AU31iQg==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@antora/logger/-/logger-3.1.6.tgz", + "integrity": "sha512-36kU8gMbPslcPu8u9EbXsz6+9G9+LWPKhO7n8mEQqxlcCqmChwgetK6VbsL102rynpgPsstX6IAKg2wbptJK5g==", "dependencies": { "@antora/expand-path-helper": "~2.0", "pino": "~8.14", @@ -143,22 +143,22 @@ } }, "node_modules/@antora/navigation-builder": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@antora/navigation-builder/-/navigation-builder-3.1.7.tgz", - "integrity": "sha512-QvMPb0qY1zfgyLCfuEhJOpO5qSVjaVe5X/bQjSii9vDGgpIEiC2yt/hgqER37E/3zsBGEZvCH5lSLk1c7x0+EQ==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@antora/navigation-builder/-/navigation-builder-3.1.6.tgz", + "integrity": "sha512-0iqktzBKQ4kgOM+pbm1bYdGUlN6Blw5zAxURr+W7X96b45mUHCTNz1nZrsDvBLbVXpSTk//ih85Ioh0RU4RJTw==", "dependencies": { - "@antora/asciidoc-loader": "3.1.7" + "@antora/asciidoc-loader": "3.1.6" }, "engines": { "node": ">=16.0.0" } }, "node_modules/@antora/page-composer": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@antora/page-composer/-/page-composer-3.1.7.tgz", - "integrity": "sha512-zJMzYznPT6Vd59nEXio/2rolkX070Nup6g4a8d4RCz0WoE8dmMidw6XFgjAHr0Lyh14/FHgBPlYXfhkDFR16Mw==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@antora/page-composer/-/page-composer-3.1.6.tgz", + "integrity": "sha512-zl2UXVmHEy23zWsGzz3ZpnqtzTVfYvAVS7osPZpSyto3unwtQUI0dR+JpWndElsEDt71JJedbsXa/y/tNC2ZOQ==", "dependencies": { - "@antora/logger": "3.1.7", + "@antora/logger": "3.1.6", "handlebars": "~4.7", "require-from-string": "~2.0" }, @@ -167,9 +167,9 @@ } }, "node_modules/@antora/playbook-builder": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@antora/playbook-builder/-/playbook-builder-3.1.7.tgz", - "integrity": "sha512-lU80S1BqUy9DvqziEzRwpYTaWhOshxgrGAjf/F5VjAIaHCGVx0rZgfoI2rgFFkbVaH94kauOngdtTXDPXC1fPQ==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@antora/playbook-builder/-/playbook-builder-3.1.6.tgz", + "integrity": "sha512-bZcDastZViAgPVPNvvbbw7ci63gL5YnyG5X7NuHJoORgzyGQAsMYEjzfa9yfNfXubUmXv/oSteUSxbACjdjzWg==", "dependencies": { "@iarna/toml": "~2.2", "convict": "~6.2", @@ -181,9 +181,9 @@ } }, "node_modules/@antora/redirect-producer": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@antora/redirect-producer/-/redirect-producer-3.1.7.tgz", - "integrity": "sha512-6zAHfcOb0v0829nAbn/3HMilbactjbjU7zBT9Iy6JHQfbqXT/g/mUT9N13Lj/wbq/nm0qKQJweB0Mi6BS2fbMw==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@antora/redirect-producer/-/redirect-producer-3.1.6.tgz", + "integrity": "sha512-tzlrJa2vny0HPBtIAgEM/xCMTfOi4z2CYUt4Ctz7rV8PBv6NOjlLkbu7Goc57CpR9wmJ3C4AGJcVHN0tah0FmA==", "dependencies": { "vinyl": "~2.2" }, @@ -192,23 +192,23 @@ } }, "node_modules/@antora/site-generator": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@antora/site-generator/-/site-generator-3.1.7.tgz", - "integrity": "sha512-39KWip9bLdQ+4ssyqjI+O0COquKHxoeznxY2/3w5pNZEoeTg8cD7kOsnWfbocw0R3Rj+kJV5MnqICFNq0nuPeA==", - "dependencies": { - "@antora/asciidoc-loader": "3.1.7", - "@antora/content-aggregator": "3.1.7", - "@antora/content-classifier": "3.1.7", - "@antora/document-converter": "3.1.7", - "@antora/file-publisher": "3.1.7", - "@antora/logger": "3.1.7", - "@antora/navigation-builder": "3.1.7", - "@antora/page-composer": "3.1.7", - "@antora/playbook-builder": "3.1.7", - "@antora/redirect-producer": "3.1.7", - "@antora/site-mapper": "3.1.7", - "@antora/site-publisher": "3.1.7", - "@antora/ui-loader": "3.1.7", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@antora/site-generator/-/site-generator-3.1.6.tgz", + "integrity": "sha512-NKRTVDGB7CuzEBx68iQGweSTDXLvqccExEfBiudPEhyagcn4U+fwef+0LjE4A2imcpD/QQC7l5U8VaNObQYnRQ==", + "dependencies": { + "@antora/asciidoc-loader": "3.1.6", + "@antora/content-aggregator": "3.1.6", + "@antora/content-classifier": "3.1.6", + "@antora/document-converter": "3.1.6", + "@antora/file-publisher": "3.1.6", + "@antora/logger": "3.1.6", + "@antora/navigation-builder": "3.1.6", + "@antora/page-composer": "3.1.6", + "@antora/playbook-builder": "3.1.6", + "@antora/redirect-producer": "3.1.6", + "@antora/site-mapper": "3.1.6", + "@antora/site-publisher": "3.1.6", + "@antora/ui-loader": "3.1.6", "@antora/user-require-helper": "~2.0" }, "engines": { @@ -216,22 +216,22 @@ } }, "node_modules/@antora/site-generator-default": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@antora/site-generator-default/-/site-generator-default-3.1.7.tgz", - "integrity": "sha512-m9UbejttKzp8MKJTEc+aKXi5SNb864QO7lQiQzSR0fiWnIR8WIM73CPPwkVeOXdKqaJvQp5IF9rlXXTkkC19fw==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@antora/site-generator-default/-/site-generator-default-3.1.6.tgz", + "integrity": "sha512-HXnqYYQdQHwjg6NAvEr3oolt4Kmb3cEVqMsR9BoN8ZPfVpnx+kM9r1/qqpgx9BZxY2oTDYPhFFeygdq2Fu5rIg==", "dependencies": { - "@antora/site-generator": "3.1.7" + "@antora/site-generator": "3.1.6" }, "engines": { "node": ">=16.0.0" } }, "node_modules/@antora/site-mapper": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@antora/site-mapper/-/site-mapper-3.1.7.tgz", - "integrity": "sha512-x++89btbwk8FxyU2+H/RHQMnsC9sdvQvXcwXwNt11eXN1qj7t8TPiQZTalg7gkf0/osY4l7JRpGBY5JJfOJVig==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@antora/site-mapper/-/site-mapper-3.1.6.tgz", + "integrity": "sha512-Jc5AqY2uS3wO21iwEFyJuXOspTLN6UdNnZP/Os2oguR+cSsjwUx+l6+X7lquIndq+dXUQS3tMQkwNkhLgfcsrw==", "dependencies": { - "@antora/content-classifier": "3.1.7", + "@antora/content-classifier": "3.1.6", "vinyl": "~2.2" }, "engines": { @@ -239,20 +239,20 @@ } }, "node_modules/@antora/site-publisher": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@antora/site-publisher/-/site-publisher-3.1.7.tgz", - "integrity": "sha512-zHaJc7UeBfFSIhBbQ5U5Ud4u671M84oqSJb3pPxlUiJDP7iVJlSl+0GNm0NAIoDizjPtVksUI88fzqCy5rfSUQ==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@antora/site-publisher/-/site-publisher-3.1.6.tgz", + "integrity": "sha512-AOpM12gmMJeucebEGneHvOJAXQgco0lAg7Vx9CH7slHVeJy6mM74Mcut7KkKlv3AOJJKgYfdYkJndvq9dqbWmQ==", "dependencies": { - "@antora/file-publisher": "3.1.7" + "@antora/file-publisher": "3.1.6" }, "engines": { "node": ">=16.0.0" } }, "node_modules/@antora/ui-loader": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@antora/ui-loader/-/ui-loader-3.1.7.tgz", - "integrity": "sha512-79QuZB0c91dveoESa3RcE18ZZFJo0rDZX9aJKHVc20dInQBGCgfURPqB2OytkzaXD3lNtDJ41yjKNYZqsAQy1Q==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@antora/ui-loader/-/ui-loader-3.1.6.tgz", + "integrity": "sha512-IivfKW7fCaV7GpXIOxyk8X2mJiYoM6U0CDaFzWiKerJeDikW1Oi9KGxCMe2+onYBJrgzQxAZsIzjr9fXUcDZWw==", "dependencies": { "@antora/expand-path-helper": "~2.0", "@vscode/gulp-vinyl-zip": "~2.5", @@ -301,9 +301,9 @@ "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==" }, "node_modules/@neo4j-antora/aliases-redirects": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@neo4j-antora/aliases-redirects/-/aliases-redirects-0.2.3.tgz", - "integrity": "sha512-r5XB4FHnMFYg2aPjpzdmyBwjKLiCcgIlmF5V6CzzgJKC06qxXPZmmQzVDsx3MS2PVRXs0rPRBjRFUqC++4Z8Xg==" + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@neo4j-antora/aliases-redirects/-/aliases-redirects-0.2.2.tgz", + "integrity": "sha512-/y0/dA0lDMRqnBeBu61LLC/sbN249efsXsM5g5Iw0Ln98bfazgU/MdfBK3ZshpADM4LQLwswDVA91p0nN40Dvw==" }, "node_modules/@neo4j-antora/antora-add-notes": { "version": "0.3.1", @@ -435,9 +435,9 @@ } }, "node_modules/async-lock": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", - "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.0.tgz", + "integrity": "sha512-coglx5yIWuetakm3/1dsX9hxCNox22h7+V80RQOu2XUUMidtArxKoZoOtHUPuR84SycKTXzgGzAUR5hJxujyJQ==" }, "node_modules/atomic-sleep": { "version": "1.0.0", @@ -1403,9 +1403,9 @@ ] }, "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", "engines": { "node": ">= 4" } @@ -1552,9 +1552,9 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "node_modules/isomorphic-git": { - "version": "1.25.6", - "resolved": "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.25.6.tgz", - "integrity": "sha512-zA3k3QOO7doqOnBgwsaXJwHKSIIl5saEdH4xxalu082WHVES4KghsG6RE2SDwjXMCIlNa1bWocbitH6bRIrmLQ==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.21.0.tgz", + "integrity": "sha512-ZqCAUM63CYepA3fB8H7NVyPSiOkgzIbQ7T+QPrm9xtYgQypN9JUJ5uLMjB5iTfomdJf3mdm6aSxjZwnT6ubvEA==", "dependencies": { "async-lock": "^1.1.0", "clean-git-ref": "^2.0.1", @@ -1798,9 +1798,9 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "node_modules/nodemon": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz", - "integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.2.tgz", + "integrity": "sha512-9qIN2LNTrEzpOPBaWHTm4Asy1LxXLSickZStAQ4IZe7zsoIpD/A7LWxhZV3t4Zu352uBcqVnRsDXSMR2Sc3lTA==", "dev": true, "dependencies": { "chokidar": "^3.5.2", @@ -2565,9 +2565,9 @@ } }, "node_modules/stream-shift": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", - "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" }, "node_modules/string_decoder": { "version": "1.3.0", @@ -3015,4 +3015,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 8f6c9dae..19054c18 100644 --- a/package.json +++ b/package.json @@ -19,24 +19,24 @@ "author": "Neo4j", "license": "ISC", "dependencies": { - "@antora/cli": "^3.1.7", - "@antora/site-generator-default": "^3.1.7", + "@antora/cli": "^3.1.5", + "@antora/site-generator-default": "^3.1.5", "@neo4j-antora/antora-add-notes": "^0.3.1", "@neo4j-antora/antora-modify-sitemaps": "^0.4.4", "@neo4j-antora/antora-page-list": "^0.1.1", "@neo4j-antora/antora-page-roles": "^0.3.2", - "@neo4j-antora/aliases-redirects": "^0.2.3", + "@neo4j-antora/aliases-redirects": "^0.2.2", "@neo4j-antora/antora-table-footnotes": "^0.3.2", "@neo4j-documentation/macros": "^1.0.2", "@neo4j-documentation/remote-include": "^1.0.0" }, "devDependencies": { "express": "^4.18.2", - "nodemon": "^3.1.0" + "nodemon": "^3.0.2" }, "overrides": { "@antora/site-generator-default": { "glob-parent": "6.0.2" } } -} +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index b8af31d1..b44a8999 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1955,4 +1955,4 @@ yazl@^2.2.1: resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35" integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw== dependencies: - buffer-crc32 "~0.2.3" + buffer-crc32 "~0.2.3" \ No newline at end of file From 7f7f48950e7826688c9e5126b4202b89bd94edf9 Mon Sep 17 00:00:00 2001 From: lidiazuin Date: Tue, 5 Mar 2024 11:59:29 +0100 Subject: [PATCH 21/23] update --- .github/workflows/docs-pr.yml | 4 +- .github/workflows/docs-teardown.yml | 2 +- modules/ROOT/pages/custom-resolvers.adoc | 1 - .../ROOT/pages/subscriptions/filtering.adoc | 804 +++++++++++++++--- 4 files changed, 672 insertions(+), 139 deletions(-) diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yml index deb198ae..e16e28c1 100644 --- a/.github/workflows/docs-pr.yml +++ b/.github/workflows/docs-pr.yml @@ -7,8 +7,8 @@ name: "Verify PR" on: pull_request: branches: - - "main" - - "dev" + - "main" + - "dev" jobs: diff --git a/.github/workflows/docs-teardown.yml b/.github/workflows/docs-teardown.yml index 38380bc3..a4cdd4bf 100644 --- a/.github/workflows/docs-teardown.yml +++ b/.github/workflows/docs-teardown.yml @@ -5,7 +5,7 @@ on: pull_request_target: branches: - "main" - - "dev" + - "dev" types: - closed diff --git a/modules/ROOT/pages/custom-resolvers.adoc b/modules/ROOT/pages/custom-resolvers.adoc index 5ce8d914..64b445eb 100644 --- a/modules/ROOT/pages/custom-resolvers.adoc +++ b/modules/ROOT/pages/custom-resolvers.adoc @@ -160,4 +160,3 @@ type Journal implements Publication { author: [Author!]! @relationship(type: "WROTE", direction: IN) } ---- - diff --git a/modules/ROOT/pages/subscriptions/filtering.adoc b/modules/ROOT/pages/subscriptions/filtering.adoc index 17289464..da5592f0 100644 --- a/modules/ROOT/pages/subscriptions/filtering.adoc +++ b/modules/ROOT/pages/subscriptions/filtering.adoc @@ -1,220 +1,754 @@ -[[troubleshooting]] -= Troubleshooting -:page-aliases: troubleshooting/faqs.adoc, troubleshooting/security.adoc, troubleshooting/optimizing-create-operations.adoc, appendix/preventing-overfetching.adoc, appendix.adoc +[[filtering]] +:description: This page covers how to apply filters to subscriptions in the Neo4j GraphQL Library. += Filtering +This page covers how to apply filters to subscriptions in the Neo4j GraphQL Library. +Note, however, that: -This chapter contains common troubleshooting steps. Additionally, there is a section for xref::troubleshooting.adoc#troubleshooting-faqs[FAQs] (Frequently Asked Questions) where you might find answers to your problems. +* Filtering can only be applied at the root of the subscription operation. +* Aggregations are not supported on subscription types and there is currently no available method. -[[troubleshooting-debug-logging]] -== Debug Logging +A subscription can be created to target the changes to a node (`create`/`update`/`delete``) or to a relationship (`create`/`delete``). -=== For `@neo4j/graphql` +While the format slightly differs depending on whether the subscription targets a node or a relationship, providing a `where` argument allows for filtering on the events that are returned to the subscription. -`@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:*` when running. For example: +== Operators -==== Command line +When creating a subscription, a number of operators are available for different types in the `where` argument. -[source, bash, indent=0] +=== Equality operators + +All types can be tested for either equality or non-equality. +For the `Boolean` type, these are the only available comparison operators. + +[[filtering-numerical-operators]] +=== Numerical operators + +The following comparison operators are available for numeric types (`Int`, `Float`, xref::type-definitions/types/index.adoc[`BigInt`]): + +* `_LT` +* `_LTE` +* `_GTE` +* `_GT` + +[NOTE] +==== +Filtering on xref::type-definitions/types/temporal.adoc[Temporal types] and xref::type-definitions/types/spatial.adoc[Spatial types] is not currently supported. +==== + +=== String comparison + +The following case-sensitive comparison operators are only available for use on `String` and `ID` types: + +* `_STARTS_WITH` +* `_ENDS_WITH` +* `_CONTAINS` + +=== Array comparison + +The following operator is available on non-array fields, and accepts an array argument: + +* `_IN` + +Conversely, the following operator is available on array fields, and accepts a single argument: + +* `_INCLUDES` + +These operators are available for all types apart from `Boolean`. + +=== `AND`/`OR` operators + +Complex combinations of operators are possible using the `AND`/ `OR` operators. +They accept as argument an array of items of the same format as the `where` argument. + +Check xref:subscriptions/filtering.adoc#combining-operators[Combining operators] for an example. + +[[node-events-usage]] +== Subscribing to node events + +The `where` argument allows specifying filters on top-level properties of the targeted nodes. +Only events matching these properties and type are returned to the subscription. + +As an example, consider the following type definitions: + +[source, graphql, indent=0] +---- +type Movie { + title: String + genre: String + averageRating: Float + releasedIn: Int +} +---- + +Now, here is how filtering can be applied when creating a subscription: + +=== `CREATE` + +To filter subscriptions to `create` operations for movies by their genre, here is how to do it: + +[source, graphql, indent=0] ---- -DEBUG=@neo4j/graphql:* node src/index.js +subscription { + movieCreated(where: {genre: "Drama"}) { + createdMovie { + title + } + } +} ---- -Alternatively, if you are debugging a particular functionality, you can specify a number of namespaces to isolate certain log lines: +This way, only newly created movies with the genre `"Drama"` trigger events to this subscription. + +[NOTE] +==== +The `where` argument only filters by properties set at the moment of creation. +==== + +=== `UPDATE` + +Here is how to filter subscription to `update` operations in movies with average rating bigger than 8: + +[source, graphql, indent=0] +---- +subscription { + movieUpdate(where: {averageRating_GT: 8}) { + updatedMovie { + title + } + } +} +---- -1. `@neo4j/graphql:*` - Logs all -2. `@neo4j/graphql:auth` - Logs the status of authorization header and token extraction, and decoding of JWT -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 +This way, you should only receive events triggered by movies with the average rating bigger than 8 when they are modified. -==== Constructor +[NOTE] +==== +The `where` argument only filters properties that already existed *before* the update. +==== -You can also enable all debug logging in the library by setting the `debug` argument to `true` in the constructor. +This is how these events may look like: -[source, javascript, indent=0] +[source, graphql, indent=0] ---- -const { Neo4jGraphQL } = require("@neo4j/graphql"); -const neo4j = require("neo4j-driver"); -const { ApolloServer } = require("apollo-server"); +mutation { + makeTheMatrix: createMovies(input: {title: "The Matrix", averageRating: 8.7}) { + title + averageRating + }, + makeResurrections: createMovies(input: {title: "The Matrix Resurrections", averageRating: 5.7}) { + title + averageRating + }, +} -const typeDefs = ` - type Movie { - title: String! +mutation { + updateTheMatrix: updateMovie( + where: {title: "The Matrix"} + update: {averageRating: 7.9} + ) { + title + }, + updateResurrections: updateMovie( + where: {title: "The Matrix Resurrections"} + update: {averageRating: 8.9} + ) { + title } -`; +} +---- + +Therefore, given the previously described subscription, these GraphQL operations should only triggered for `"The Matrix"` movie. -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("username", "password") -); +=== `DELETE` -const neoSchema = new Neo4jGraphQL({ - typeDefs, - driver, - debug: true, -}); +Here is how to filter subscription to `delete` operations in movies by their genre, using the `NOT` filter: + +[source, graphql, indent=0] ---- +subscription { + movieDeleted(where: { NOT: { genre: "Comedy" } }) { + deletedMovie { + title + } + } +} +---- + +This way, only deleted movies of all genres except for `"Comedy"` should trigger events to this subscription. + +[NOTE] +==== +The `where` argument only filters properties that already existed before the deletion process. +==== -=== For `@neo4j/introspector` +[[combining-operators]] +=== Combining operators -`@neo4j/introspector` has its own debug logging namespace and you can turn on logging for it with: +All previously mentioned operators can be combined using the `AND`, `OR`, and `NOT` operators. +They accept an array argument with items of the same format as the `where` argument, which means they can also be nested to form complex combinations. -[source, bash, indent=0] +As an example, consider a user who likes comedy movies, but not romantic comedies from early 2000, and who has the Matrix Trilogy as their favorite titles. +They could subscribe to any updates that cover this particular set of interests as follows: + +[source, graphql, indent=0] ---- -DEBUG=@neo4j/introspector node src/index.js +subscription { + movieUpdated(where: { + OR: [ + { title_CONTAINS: "Matrix" }, + { genre: "comedy" }, + { AND: [ + { NOT: { genre: "romantic comedy" } }, + { releasedIn_GT: 2000 }, + { releasedIn_LTE: 2005 } + ] }, + ] + }) { + updatedMovie { + title + } + } +} ---- -Read more about the xref::introspector.adoc[introspector]. -[[troubleshooting-query-tuning]] -== Query Tuning +== Subscribing to relationship events + +When subscribing to relationship events, the `where` argument still allows specifying filters on the top-level properties of the targeted nodes. +It also supports specifying filters on the relationship properties (`edge`) and on the top-level properties (`node`) of the nodes at the other end of the relationship. +This is done by using the operators previously described, and the usage is very similar to xref:subscriptions/filtering.adoc#node-events-usage[subscribing to node events]. + +However, filtering by relationship events is an even more powerful logic. +This is because these filters can also express the expected relationship field, or the expected concrete type at the other end of the relationship, provided that they are connected to abstract types. -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 in the request context. +Note that each relationship field specified is combined with the others using a xref:subscriptions/filtering.adoc#filter-logical-or[logical `OR`]. +Only events matching these relationship field names are returned in the subscription. -You can read more about the available query options at https://neo4j.com/docs/cypher-manual/current/query-tuning/query-options/#cypher-query-options[Cypher Manual -> Query Options]. +You can further filter each relationship field by node and relationship properties. +These fields are combined in the resulting filter with a xref:subscriptions/filtering.adoc#filter-logical-and[logical `AND`]. -_Please only set these options if you know what you are doing._ +As an example, in the following type definitions: + +[source, graphql, indent=0] +---- +type Movie { + title: String + genre: String + actors: [Actor!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN) +} -For example, to set the "runtime" option to "interpreted": +type ActedIn @relationshipProperties { + screenTime: Int! +} -[source, javascript, indent=0] +type Actor { + name: String +} ---- -const { Neo4jGraphQL } = require("@neo4j/graphql"); -const neo4j = require("neo4j-driver"); -const { ApolloServer } = require("apollo-server"); -const typeDefs = ` - type Movie { - title: String! +The format of the `where` argument is: + +[source, graphql, indent=0] +---- +{ + movie: { + # top-level properties of the node targeted for the subscription operation, supports operators + title_IN: ["The Matrix", "Fight Club"] + }, + createdRelationship: { + actors: { # field name corresponding to a relationship in the type definition of the node targeted for the subscription operation + edge: { + # properties of the relationship, supports operators + screenTime_GT: 10, + }, + node: { + # top-level properties of the node on the other end of the relationship, supports operators + name_STARTS_WITH: "Brad" + } + } } -`; +} +---- -const driver = neo4j.driver( - "bolt://localhost:7687", - neo4j.auth.basic("username", "password") -); +The following sections feature examples of how filtering can be applied when creating a subscription to relationship events. -const neoSchema = new Neo4jGraphQL({ - typeDefs, - driver, -}); +=== Newly created relationship -neoSchema.getSchema().then((schema) => { - const server = new ApolloServer({ - schema, - context: ({ req }) => ({ - req, - cypherQueryOptions: { - runtime: "interpreted", - }, - }), - }); +The following example filters the subscriptions to newly created relationships that are connecting a `Movie` from genres other than "Drama", and to an `Actor` with a screen time bigger than 10 minutes: - server.listen().then(({ url }) => { - console.log(`Server ready at ${url}`); - }); -}); +[source, graphql, indent=0] ---- +subscription { + movieRelationshipCreated(where: { movie: { NOT: { genre: "Drama" } }, createdRelationship: { actors: { edge: { screenTime_GT: 10 } } } }) { + movie { + title + } + createdRelationship { + actors { + screenTime + node { + name + } + } + } + } +} +---- + +[NOTE] +==== +The `where` argument only filters already existing properties at the moment of the relationship creation. +==== -[[troubleshooting-faqs]] -== FAQs +=== Newly deleted relationship -This chapter contains commonly asked questions and their solutions. +The following example filters the subscriptions to deleted relationships that were connecting a `Movie` of the genre `"Comedy"` or `"Adventure"` to an `Actor` named `"Jim Carrey"`: -=== I've upgraded from <1.1.0 and my `DateTime` fields aren't sorting as expected +[source, graphql, indent=0] +---- +subscription { + movieRelationshipDeleted(where: { movie: { genre_IN: ["Comedy", "Adventure"] }, createdRelationship: { actors: { node: { name: "Jim Carrey" } } } }) { + movie { + title + } + deletedRelationship { + actors { + screenTime + node { + name + } + } + } + } +} +---- -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: +[NOTE] +==== +The `where` argument only filters properties that already existed before the relationship deletion. +==== -[source, javascript, indent=0] +=== Relationship-related filters + +In addition to filtering node or relationship properties, the relationship-related filtering logic is even more powerful. +This is because these filters can also express the expected relationship field, or the expected concrete type at the other end of the relationship, provided that they are connected to abstract types. + +The following examples are valid for both `CREATE_RELATIONSHIP` and `DELETE_RELATIONSHIP` events. +Their purpose is to illustrate the various ways in which a subscription to a relationship event can be filtered. + +Considering the following type definitions: + +[source, graphql, indent=0] ---- -MATCH (m:Movie) -WHERE apoc.meta.type(m.timestamp) = "STRING" -SET m.timestamp = datetime(m.timestamp) -RETURN m +type Movie { + title: String + genre: String + actors: [Actor!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN) + directors: [Director!]! @relationship(type: "DIRECTED", properties: "Directed", direction: IN) + reviewers: [Reviewer!]! @relationship(type: "REVIEWED", properties: "Review", direction: IN) +} + +type ActedIn @relationshipProperties { + screenTime: Int! +} + +type Actor { + name: String +} + +type Person implements Reviewer { + name: String + reputation: Int +} + +union Director = Person | Actor + +type Directed @relationshipProperties { + year: Int! +} + +interface Reviewer { + reputation: Int! +} + +type Magazine implements Reviewer { + title: String + reputation: Int! +} + +type Review @relationshipProperties { + score: Int! +} ---- -=== I've created some data and then gone to query it, but it's not there +And the base subscription operation: -If you use a causal cluster or an Aura Professional instance, there is a chance that the created data is not yet present on the server which gets connected to on the next GraphQL query. +[source, graphql, indent=0] +---- +subscription MovieRelationshipDeleted($where: MovieRelationshipDeletedSubscriptionWhere) { + movieRelationshipDeleted(where: $where) { + movie { + title + } + deletedRelationship { + actors { + screenTime + node { + name + } + } + directors { + year + node { + ... on PersonEventPayload { # generated type + name + reputation + } + ... on ActorEventPayload { # generated type + name + } + } + } + reviewers { + score + node { + reputation + ... on MagazineEventPayload { # generated type + title + reputation + } + ... on PersonEventPayload { # generated type + name + reputation + } + } + } + } + } +} +---- + +You can use the following `where` inputs in the GraphQL variable values to get different results: -You can ensure that the data is available to query by passing a bookmark into your request - see xref::driver-configuration.adoc#driver-configuration-bookmarks[Specifying Neo4j Bookmarks] for more information. +==== Filtering via implicit/explicit declaration -=== What is `_emptyInput` in my update and create inputs? +Implicit or explicit declaration is used to filter specific relationship types that are expected to be returned to a subscription. -`_emptyInput` will appear in your update and create inputs if you define a type with only auto-generated and/or relationship properties. It is a placeholder property and therefore giving it a value in neither update nor create will give it a value on the node. `_emptyInput` will be removed if you add a user-provided property. +For example, when subscribing to created or deleted relationships to a `Movie`, a user might only be interested in the relationship of type `ACTED_IN`, but indifferent to the properties of the `Actor` node or the other relationships connected to it. +In this case, the corresponding field name of this relationship is `actors`. -The following example will create inputs with `_emptyInput`: +By explicitly specifying the `actors` field name, you can filter-out events to other relationship properties: -[source, graphql] +[source, graphql, indent=0] ---- -type Cookie { - id: ID! @id - owner: Owner! @relationship(type: "HAS_OWNER", direction: OUT) - # f: String # If you don't want _emptyInput, uncomment this line. +{ + where: { + deletedRelationship: { + actors: {} # no properties specified here, therefore all relationships to this field name will be returned + } + } } ---- -=== Relationship nullability isn't being enforced in my graph +In case you are interested in `Actor` nodes conforming to some filters, for example with the name starting with the letter "A", the procedure is no different than xref:subscriptions/filtering.adoc#node-events-usage[subscribing to node events]: + +[source, graphql, indent=0] +---- +{ + where: { + deletedRelationship: { + actors: { + node: { # use operations to specify filers on the top-level properties of the node at the other end of the relationship + name_STARTS_WITH: "A" + } + } + } + } +} +---- -Currently, and given the typeDefs below, Neo4j GraphQL will enforce cardinality when creating and updating a one-one relationship such as the movie director field below: +If you are also interested in the relationship itself conforming to some filters, such as the `Actor` having spent no more than 40 minutes in the `Movie`, this is how the query may look: [source, graphql, indent=0] ---- -type Movie { - title: String! - director: Person! @relationship(type: "DIRECTED", direction: IN) - actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN) +{ + where: { + deletedRelationship: { + actors: { + edge: { # use operations to specify filers on the top-level properties of the relationship + screenTime_LT: 40, + } + node: { + name: "Alvin" + } + } + } + } } +---- + +Multiple relationship types can also be included in the returned subscriptions by explicitly specifying the corresponding field names. +For instance: -type Person { - name: String! +[source, graphql, indent=0] +---- +{ + where: { + deletedRelationship: { + actors: {}, # include all relationships corresponding of type `ACTED_IN` + directors: {} # include all relationships corresponding of type `DIRECTED` + # exclude relationships of type `REVIEWED` + } + } } ---- -However, at this point, there is no mechanism to support validating the actors relationship. Furthermore, there is a known limitation given if you were create a movie and a director in one mutation: +Now, if you are interested in all relationship types, you can either express this implicitly by not specifying any: [source, graphql, indent=0] ---- -mutation { - createMovies( - input: [ - { - title: "Forrest Gump" - director: { create: { node: { name: "Robert Zemeckis" } } } - } - ] - ) { - movies { - title - director { - name - } +{ + where: { + deletedRelationship: {} # include all relationships of all types } - } } ---- -Then delete the director node: +Or explicitly by specifying the field names of all the relationships connected to the type targeted for the subscription: [source, graphql, indent=0] ---- -mutation { - deletePeople(where: { name: "Robert Zemeckis" }) { - nodesDeleted - } +{ + where: { + deletedRelationship: { + # include all relationships of all types + # subscription target type is `Movie`, which has the following relationship field names: + actors: {}, + directors: {}, + reviewers: {} + } + } +} +---- + +Note, however, that as **any** filters are applied to **any** of the relationships, explicitly including those that you are interested in subscribing to is a **mandatory** step. + +For example, if all relationships should be returned, but you want to filter-out the `REVIEWED` ones which have a score lower than 7, this is how your query may look like: + +[source, graphql, indent=0] +---- +{ + where: { + deletedRelationship: { + actors: {}, # include all relationships of type `ACTED_IN` + directors: {}, # include all relationships of type `DIRECTED` + reviewers: { # include all relationships of type `REVIEWED`, with the score property greater than 7 + edge: { + score_GT: 7 + } + } + } + } +} +---- + +Different filters can also be applied to different relationships without any constraints. +For example: + +[source, graphql, indent=0] +---- +{ + where: { + deletedRelationship: { + actors: { # include some relationships of type `ACTED_IN`, filtered by relationship property `screenTime` and node property `name` + edge: { + screenTime_LT: 60, + }, + node: { + name_IN: ["Tom Hardy", "George Clooney"] + } + }, + directors: {}, # include all relationships of type `DIRECTED` + reviewers: { # include some relationships of type `REVIEWED`, filtered by relationship property `score` only + edge: { + score_GT: 7 + } + } + } + } +} +---- + +[[filter-logical-or]] + +[NOTE] +==== +In the previous example, there is an implicit logical `OR` between the `actors`, `directors`, and `reviewers` relationship fields. +This is to say that a relationship of **either** type `ACTED_IN` **or** of type `DIRECTED` **or** of type `REVIEWED` should trigger the previously described subscription. +==== + +[[filter-logical-and]] +[NOTE] +==== +There is an implicit logical `AND` between the `edge` and `node` fields inside of the `actors` relationship field. +In other words, the relationship of type `ACTED_IN` with the property `screenTime` less than 60 **and** a target node with name in `["Tom Hardy", "George Clooney"]` should trigger the subscription. +==== + +=== Abstract types + +The following sections describe how to filter subscriptions using abstract types. + +==== Union type + +This example illustrates how to filter the node at the other end of the relationship when it is of a union type: + +[source, graphql, indent=0] +---- +{ + where: { + deletedRelationship: { + directors: { # relationship to a union type + Person: { # concrete type that makes up the union type + edge: { + year_GT: 2010 + }, + node: { + name: "John Doe", + reputation: 10 + } + }, + Actor: { # concrete type that makes up the union type + edge: { + year_LT: 2005 + }, + node: { + name: "Tom Hardy" + } + } + }, + } + } +} +---- + +The result is that only relationships of type `DIRECTED` are returned to the subscription, where the `Director` is a `Person` named `"John Doe"`, who directed the movie after 2010, **or** where the `Director` is an `Actor` named `"Tom Hardy"` who directed the movie before 2005. + +Note that the relationship field name is split into multiple sections, one for each of the concrete types that make up the union type. +The relationship properties do not exist outside the confines of one of these sections, even though the properties are the same. + +Now, take the other example that did not explicitly specify the concrete types: + +[source, graphql, indent=0] +---- +{ + where: { + deletedRelationship: { + directors: {}, # include all relationships of type `DIRECTED` + } + } } ---- -No error is thrown, even though the schema states that all movies must have a director thus technically rendering the movie node invalid. +Following the same logic as for the relationship field names: when nothing is explicitly provided, then all is accepted. +Thus relationships of type `DIRECTED`, established between a `Movie` and any of the concrete types that make up the union type `Director` are returned to the subscription. -Finally, we do not enforce relationship cardinality on union or interface relationships. +It is equivalent to the following: -[[security]] -== Security +[source, graphql, indent=0] +---- +{ + where: { + deletedRelationship: { + directors: { # include all relationships of type `DIRECTED` + Actor: {}, + Person: {} + } + } + } +} +---- -This section describes security considerations and known issues. +Note that explicitly specifying a concrete type excludes the others from the returned events: -=== Authorization not triggered for empty match +[source, graphql, indent=0] +---- +{ + where: { + deletedRelationship: { + directors: { + Actor: {} # include all relationships of type `DIRECTED` to an `Actor` type + } + } + } +} +---- + +In this case, only relationships of type `DIRECTED` between a `Movie` and an `Actor` are returned to the subscription. +Those between a `Movie` and a `Person` are filtered out. + +One reason why this might be done is to include some filters on the `Actor` type: + +[source, graphql, indent=0] +---- +{ + where: { + deletedRelationship: { + directors: { + Actor: { # include some relationships of type `DIRECTED` to an `Actor` type, that conform to the filters + node: { + NOT: { name: "Tom Hardy" } + } + } + } + } + } +} +---- + +To include filters on the `Actor` type, but also include the `Person` type in the result, you need to make the intent explicit: + +[source, graphql, indent=0] +---- +{ + where: { + deletedRelationship: { + directors: { + Actor: { # include some relationships of type `DIRECTED` to an `Actor` type, that conform to the filters + node: { + NOT: { name: "Tom Hardy" } + } + }, + Person: {} # include all relationships of type `DIRECTED` to a `Person` type + } + } + } +} +---- + + +==== Interface type + +The following example illustrates how to filter the node at the other end of the relationship when it is of an interface type: + +[source, graphql, indent=0] +---- +{ + where: { + deletedRelationship: { + reviewers: { # relationship to an interface type + edge: { + # relationship properties of a relationship of type `REVIEWED` + score_GT: 7 + }, + node: { + # common fields declared by the interface + reputation_GTE: 8 + } + }, + } + } +} +---- -If a query yields no results, the xref::authentication-and-authorization/authorization.adoc[Authorization] process will not be triggered. -This means that the result will be empty, instead of throwing an authentication error. Unauthorized users may -then discern whether or not a certain type exists in the database, even if data itself cannot be accessed. \ No newline at end of file +This example returns events for relationships between the type `Movie` and `Reviewer`, where the score is higher than 7, and the `Reviewer` has a reputation greater or equal to 7. \ No newline at end of file From b6e90fe95603ef5f7cb45ae5e393912e595cca62 Mon Sep 17 00:00:00 2001 From: lidiazuin Date: Tue, 5 Mar 2024 12:02:10 +0100 Subject: [PATCH 22/23] update --- modules/ROOT/pages/custom-resolvers.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/ROOT/pages/custom-resolvers.adoc b/modules/ROOT/pages/custom-resolvers.adoc index 64b445eb..5ce8d914 100644 --- a/modules/ROOT/pages/custom-resolvers.adoc +++ b/modules/ROOT/pages/custom-resolvers.adoc @@ -160,3 +160,4 @@ type Journal implements Publication { author: [Author!]! @relationship(type: "WROTE", direction: IN) } ---- + From c32245df410a3a6a27592aff0fdf57552a7ce6a7 Mon Sep 17 00:00:00 2001 From: lidiazuin Date: Tue, 5 Mar 2024 13:01:34 +0100 Subject: [PATCH 23/23] removing link to unavailable page --- .../type-definitions/directives/indexes-and-constraints.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/pages/type-definitions/directives/indexes-and-constraints.adoc b/modules/ROOT/pages/type-definitions/directives/indexes-and-constraints.adoc index ec9b3bc5..7b19d80b 100644 --- a/modules/ROOT/pages/type-definitions/directives/indexes-and-constraints.adoc +++ b/modules/ROOT/pages/type-definitions/directives/indexes-and-constraints.adoc @@ -225,7 +225,7 @@ query { == Asserting constraints -In order to ensure that the specified constraints exist in the database, you need to run the function `assertIndexesAndConstraints` (see more details in xref::reference/api-reference/neo4jgraphql.adoc#api-reference-assertconstraints[API reference]). +In order to ensure that the specified constraints exist in the database, you need to run the function `assertIndexesAndConstraints`. A simple example to create the necessary constraints might look like the following, assuming a valid driver instance in the variable `driver`. This creates two constraints, one for each field decorated with `@id` and `@unique`, and apply the indexes specified in `@fulltext`: