Giter Club home page Giter Club logo

graphql's Introduction

Neo4j GraphQL Library

๐Ÿ’ก Welcome to the Monorepo for Neo4j + GraphQL.


GraphQL API for Aura
Weโ€™ve been working to make GraphQL natively available in Aura, Neo4j's Cloud based Graph Database service, and we are now ready to let people try it in an Early Access Program that will run from April to the end of May. There will be at least one more iteration with additional features before a full release later this year.

This will offer

  • Use of GraphQL API with an Aura instance in GCP
  • Use of Aura CLI to configure the GraphQL API
  • Authentication to the GraphQL API using an API Key or a 3rd party Identity Provider that supports OpenID Connect 2.0 with a JWKS endpoint for JWT validation
  • A Discord based community to post feedback and questions that you have

Using the GraphQL API during the EAP with AuraDB instances being charged as normal will be at no cost.

Would you be interested in taking part? Or want to know more? Then register here:- GraphQL API EAP and you'll be contacted nearer the date.


Neo4j + GraphQL

Discord Discourse users

Contributing

The default branch for this repository is dev, which contains changes for the next release. This is what you should base your work on if you want to make changes.

Want to contribute to @neo4j/graphql? See our contributing guide and development guide to get started!

Links

Navigating

This is a TypeScript Monorepo managed with Yarn Workspaces. To learn more on how to; setup, test and contribute to Neo4j GraphQL then please visit the Contributing Guide.

Media

Blogs, talks and other content surrounding Neo4j GraphQL. Sign up for NODES 2023 to view even more Neo4j GraphQL content.

Learn with GraphAcademy

Learn the fundamentals of GraphQL and how to use the Neo4j GraphQL Toolbox and the Neo4j GraphQL Library to create Neo4j-backed GraphQL APIs with the Introduction to Neo4j & GraphQL on GraphAcademy.

graphql's People

Contributors

a-alle avatar andy2003 avatar angrykoala avatar danstarns avatar darrellwarde avatar davidoliversp2 avatar dmoree avatar dvanmali avatar farhadnowzari avatar gis-consulting-gmbh avatar happenslol avatar kozak-codes avatar lackofmorals avatar liam-doodson avatar lidiazuin avatar litewarp avatar macondoexpress avatar marius56782 avatar mathix420 avatar mhlz avatar mjfwebb avatar nelsonpecora avatar neo-technology-build-agent avatar neo4j-team-graphql avatar oskarhane avatar rcbevans avatar recrwplay avatar renovate-bot avatar renovate[bot] avatar tbwiss avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphql's Issues

Subscriptions

Is your feature request related to a problem? Please describe.
Looking for the ability to subscribe to events via the Neo4j GraphQL API.

Additional context
Came from here #179

Proposal allow aliasing fields

Provide an @property directive to change the name used for the database mappings.

With schema:

type Person {
  id : ID! @property(name:"an-id")
  name: String @property(name:" a name ")
}

And query:

{ person(id:"32",name:"Jane") { name }}

Should generate something like:

MATCH (person:Person)
WHERE (person.`an-id` = $personId
	AND person.` a name ` = $personName)
RETURN person {
	name: person.` a name `
} AS person

see also: https://github.com/neo4j-graphql/neo4j-graphql-java/blob/master/core/src/test/resources/property-tests.adoc

Using @exclude directive on apollo server with ogm.neoSchema breaks apollo server

Describe the bug
Using any@exclude directives breaks apollo server when using's OGM's neoSchema vs Neo4JGraphQL's neoSchema

Type:

type Error @exclude {
  code: String!
  message: String!
}
type ErrorResponse @exclude {
  error: Error!
}

Error:

[ERROR] 11:33:51 Error: Input Object type ErrorResponseCreateInput must define one or more fields.

To Reproduce
Steps to reproduce the behavior:

export const ogm = new OGM({
  typeDefs,
  driver,
  resolvers,
  config: {
    jwt: {
      secret: process.env.JWT_SECRET!,
    },
  },
});

export const neoSchema = new Neo4jGraphQL({
  typeDefs,
  driver,
  resolvers,
  config: {
    jwt: {
      secret: process.env.JWT_SECRET!,
    },
  },
});

Works:

const server = new ApolloServer({
  schema: neoSchema.schema
});

Returns Error:

const server = new ApolloServer({
  schema: ogm.neoSchema.schema
});

Conditional @auth(orization)

I'd like to be able to create custom authorization checks utilizing the @cypher directive. An example of this would be to check if one user is connected to another user via a matched path (that can't be matched via a @relationship):

type Group {
  id: ID! @id
  name: String!
  members: [User!] @relationship(type: ":MEMBER_OF", direction: IN)
}

type User {
  id: ID! @id
  name: String!
  groups: [Group!] @relationship(type: ":MEMBER_OF", direction: OUT)

  isConnected: Boolean @cypher(statement: """
    MATCH (this)-[:MEMBER_OF]->(:Group)<-[:MEMBER_OF]-(u:User)
      WHERE u.id = $auth.jwt.sub
    RETURN count(u) > 0
  """)
}

extend type User {
  @auth(rules: [{
    operations: [READ]
    allow: {isConnected: true}
  }])
}

This would allow the current user to view another user only if they were connected via a shared Group.

I can think of many other situations where this kind of flexibility would be useful and prevent the need for creating custom resolvers. It might even solve #159.

Proposal: add ability to filter via @auth directive

Currently querying nodes, which are not accessible by the user will throw an forbidden error.

For the given schema:

type User {
    id: ID!
    name: String!
}

extend type User @auth(
    rules: [
        { 
            operations: ["read"],
            allow: { id: "$jwt.sub" }
        }
    ]
)

The query:

{
    users {
        id
    }
}

with the cypher:

MATCH (this:User)
CALL apoc.util.validate(NOT(EXISTS(this.id) AND this.id = $this_auth_allow0_id), "@neo4j/graphql/FORBIDDEN", [0])
RETURN this {
    .id
} as this

will always fail if there are multiple users in the database.

It would be useful to have a mechanism to control the behavior for non matching nodes, like Error and Filter.

Error: Type with name "DateTime" does not exists

Describe the bug
When using a custom Query or Mutation and you have a reference type, that has a DateTime:

type Node {
  createdAt: DateTime
}

type Query {
  nodes: [Node] @cypher(statement: "")
}

Error is thrown:

node_modules\graphql-compose\lib\TypeStorage.js:44
      throw new Error(`Type with name ${(0, _misc.inspect)(typeName)} does not exists`);
      ^

Error: Type with name "DateTime" does not exists
    at SchemaComposer.get (node_modules\graphql-compose\lib\TypeStorage.js:44:13)

If you remove the type Query or similar type Mutation it works!

System

Proposal: allow passing additional parameters to `@cypher`

e.g. a use case for this might be that there is a @cypher-defined property that fetches a list of records. It would be useful to be able to add parameters that can be used by the user-defined query e.g. pass in a number, X, and use it to only retrieve records newer than X days old.

It seems to me that at the database and resolver level this would be fairly straightforward to implement, but more problematic when it comes to designing the directive and schema. I'm not gonna even try to propose what it might look like as I can't think of anything that isn't quite ugly/hacky.

assertSchema & seachSchema

Hey guys, while I am really excited about this new release, I find that there are a few powerful features I really enjoy in the previous neo4j-graphql.js library are missing in this release. I personally use assertSchema and seachSchema a lot, so I wonder if there would be a further update so that I can keep my use of @index @unique @search in my graphql schema.

Proposal: add support for relation filters _some, _none, _single and _every

In the java version we currently support the relation filters _some, _none, _single and _every

As stated by @danstarns in slack:

Good point - _single and _some are missing. We removed _none because its the same as:

{
  User(filter: { posts_not: { id: 1 } }) {
    id
  }
}

And _every is the same as:

{
  User(filter: { posts: { id: 1 } }) {
    id
  }
}

I would suggest to distinguish between n..1 and n..m relations:

Filter for n..m-relations

I realized that users may use the _every but expecting it to act like the _some filter:

{
  User(filter: { posts: { id: 1 } }) {
    id
  }
}

That's why I would vote for make all 4 filters explicit:

  • _every: Filters only those ${type.name} for which all ${field.name}-relationships matches this filter
  • _some: Filters only those ${type.name} for which at least one ${field.name}-relationship matches this filter
  • _single: Filters only those ${type.name} for which exactly one ${field.name}-relationship matches this filter
  • _none: Filters only those ${type.name} for which none of the ${field.name}-relationships matches this filter

and remove <field>_not and <field> for n..m relations!
It's much clearer to the user what he can expect as result, especially with the given description on each field.

Filter for n..1-relations

The filter above does not make sense for n..1 relations, so I would keep the current API with <field>_not and <field> for these types of relations.

Attributes on @relationship directive

Previously you were able to define relationship attributes like this:

  type Cause {
    id: ID! @id

    "Every Cause has at least one Effect"
    effects: [Effect]
  }

  type Effect @relation(
    from: "causedBy", 
    to: "causes"
  ) {
    "Every Effect is caused by a Cause"
    causedBy: Cause

    "Every Effect causes a Cause"
    causes: Cause

    description: String
  }

It doesn't seem possible to define a Type with an @relationship directive.

Will this feature come back in subsequent releases?

Proposal: Make created or modified timestamps explicit

In the java world (JPA) we have annotations -which are directives in graphql - for auditing data

Instead of using one annotation to cover all usecases like: @autogenerate(operations: ["create"]) I would suggest to use different directives like:

  • @CreatedDate
  • @LastModifiedDate

In future also:

  • @CreatedBy
  • @LastModifiedBy

auth/roles integration tests fail if run against an empty database

Describe the bug
The following tests in packages/graphql/tests/integration/auth/roles.int.test.ts fail if they are run against an empty database:

  • auth/is-authenticated โ€บ read โ€บ should throw if not authenticated type definition
  • auth/roles โ€บ read โ€บ should throw if missing role on type definition
  • auth/roles โ€บ update โ€บ should throw if missing role on type definition (only if run in isolation)
  • auth/roles โ€บ delete โ€บ should throw if missing role on type definition (only if run in isolation)

This bug, and the fact that the behaviour varies based on whether the tests are run in isolation, suggests that individual tests are relying on data produced by other tests to pass.

More worryingly, it seems that if there are no records for a type labelled with an @auth directive, it seems that the library will allow unauthenticated/unauthorized users to figure out the fact that there is no data. Not a massive breach, but they shouldn't even be able to do that.

To Reproduce
Steps to reproduce the the top two failures:

  1. Empty your database: MATCH (n) DETACH DELETE n
  2. Run the offending integration tests: yarn test:int
  3. See error

Steps to reproduce the the bottom two failures:

  1. Empty your database: MATCH (n) DETACH DELETE n
  2. Run the offending integration tests: yarn test:int -t "should throw if missing role on type definition"
  3. See error

Expected behavior
Tests to pass, @auth directive to block unauthenticated/unauthorized users from seeing if there is no data for a certain type.

System

  • OS: macOS
  • Version: master
  • Node.js version: 14.16.0

Subscriptions and Relationship property

I'm working on a project with a team that want to utilize Graphql with neo4j. I think this library is nice but i would like to request the subscriptions and Relationship properties as they will be crucial in the project.

Query as an @auth option

Is your feature request related to a problem? Please describe.
As a developer, I need to have a flexible option validate date as a condition for authenticity.

Describe the solution you'd like
I would like to have an option to have a rule called "query" who's value is a graphql query. The query is evaluated just as any other query as a condition. If the condition is true, then the operation is authenticated.

@auth(rules: [{                         // Authenticated
    operations: [CREATE],
    isAuthenticated: true,
    query: {
        match: "bob",
        gql:  `users(where: {name: "bob"}) { name }`
    }
}]) 

@auth(rules: [{                         // NOT Authenticated
    operations: [CREATE],
    isAuthenticated: true,
    query: {
        match: "allen",
        gql:  `users(where: {name: "bob"}) { name }`
    }
}])

const myAuthQuery = gql`
    users(where: {
        name: "bob"
    }) {
        name
    }
`

@auth(rules: [{                         // Authenticated
    operations: [CREATE],
    isAuthenticated: true,
    query: {
        match: "bob",
        gql: myAuthQuery 
    }
}])

@auth(rules: [{                         // Authenticated
    operations: [CREATE],
    isAuthenticated: true,
    query: {
        match: "bob",
        cypher: "MATCH (u:User {name: "bob"}) return u.name"
    }
}])


@auth(rules: [{                         // NOT Authenticated
    operations: [CREATE],
    isAuthenticated: true,
    query: {
        match: "allen",
        cypher: "MATCH (u:User {name: "bob"}) return u.name"
    }
}])

Describe alternatives you've considered
I have implemented on the client by calling similar queries as validation, but that still potentially leave the endpoint compromised, or I have to limit application functionality.

Additional context
Nope... just the code example above. PS... this is a direct functionality lift of another graphq DB's graphql solution.

Granular security for Update

Is your feature request related to a problem? Please describe.
Currently security setup for Update mutation is all or none, we cannot define permissions for certain fields

Allow defining undirected relations

In the java version the user can define undirected relations.

This feature is currently missing here.

Schema:

type User
{
  name: String!
  uuid: ID!
  associates: [User!] @relation(name:"ASSOCIATES_WITH", direction:BOTH)
}

Query:

query {
  user(uuid: $uuid) {
    uuid
    name
    associates { name uuid }
  }
}

Cypher:

MATCH (user: User)
WHERE user.uuid = $uuid
RETURN user {
    .uuid,
    .name,
    associates: [(user)-[: ASSOCIATES_WITH]-(userAssociates: User) | userAssociates {
        .name,
        .uuid
    }]
} AS user

Injecting a custom parameter via cypherParams

Describe the regression
I am currently migrating an existing application from neo4j-graphql-js to this official library and can't seem to find a way to implement the following scenario:

  • A value is sent as a header along with the GraphQL request
  • Before passing it to Neo4j-graphql, we call an external API with this value and get a parameter that would be passed to the cypher query
  • We put it in cypherParams.myValue and inject into the cypher when specifying the GraphQL schema

As far as I can see, there is only functionality to get values from JWT, but no way of (synchronously or asynchronously) fetching and injecting custom parameters.

Last working version

neo4j-graphql-js

@neo4j/[email protected]

System (please complete the following information):

Any

Proposal: make operations in @exclude an enum

I would suggest to make the values of the operations argument an enum. This makes it clearer to the user what values are available.

So for * an enum value of ALL could be used.

So the directive declaration would be:

directive @exclude(operation:[ExcludeOptions!]! = [ALL]) on OBJECT
enum ExcludeOptions{
  CREATE,
  READ,
  UPDATE,
  DELETE
  ALL
}

setting the default value to ALL would allow to use the exclude directive without argument, so one can use it like:

type Actor @exclude {
  name: String
}

Autogenerated `MERGE` Mutations (`updateOrCreate`)

Please consider adding autogenerated merge mutations similar to those in neo4j-graphql-js.

Merge mutations are very convenient for my use case of ingesting event data since it simplifies the code and allows idempotent replay of events if needed.

This feature would help transition from neo4j-graphql-js to neo4j/graphql

Proposal: Change negative filters to include null results

While the new library is in alpha I thought it's a good point to address IMHO the biggest niggle in neo4j-graphql-js. The following filter:

{
	Systems(filter: {
		lifecycleStage_not: Decommissioned
	}) {
		name
		lifecycleStage
	}
}

excludes Systems whose lifecycleStage is not set, which is quite unintuitive and is pretty much never what any of the users of our API want or expect. As a consequence, our codebases are littered with long-winded filters such as the following:

{
	OR: [{lifecycleStage_not: Decommissioned}, {lifecycleStage: null}]
}

Return Primitive Fields to GraphQL layer

Is your feature request related to a problem? Please describe.
Currently, if you want to add a computed field on the Node through a custom resolver you need the client to fetch all the dependent fields in order to compute the result. For example, to combine and first and last name as a fullName (as shown in the docs), the client needs to request firstname and lastname in the query in order for the server to have access to the first and last name. This is inefficient from a client / graphql standpoint as we should only be fetching the data we need from the server. It would be better if we had access to all, or certain, of the primitive fields on the server such that there is a parent reference or object to extend.

Describe the solution you'd like
Ideally one of the following: (i) return all primitive fields on the node from the db to the GraphQL layer, (ii) a directive to include fields in the server request, and/or (iii) at least return the primary key or indicated @id for the node.

Describe alternatives you've considered
N/A. Without a reference to the parent node, one can't even query for the additional fields.
Additional context
This may also help you reduce the demand for the relationship properties as users like myself could build whatever connections we wanted rather than having to rely on the enhanced schema. This is akin to more traditional GraphQL endpoints where a query on a Model or document returns the row or document reference.

Add @Unique Directive Back

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
I would like to enforce the uniqueness of certain fields, which I was able to do using neoj4-graphql-js.
Describe the solution you'd like
A clear and concise description of what you want to happen.
@unique directive should give the nodes property a unique constraint
Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
Otherwise, I have to rely on developers not writing bugging code

Additional context
Add any other context or screenshots about the feature request here.

Why ALL is used instead of ANY when converting where query filters to cypher?

Describe the bug
I have a use case where one node (e.g. User) is linked to multiple nodes of the same label (e.g. User Demographics), each User can be linked to one or more different types of demographics depending on the client the users are under. I wanted to write a query to fetch users that have specific demographic data. But the results I am getting for the related queries is not expected. Below explains the use case and the result I am getting and what I actually expected.

Type definitions

type User {
    client_id: String
    uid: String
    demographics: [UserDemographics] @relationship(type: "HAS_DEMOGRAPHIC", direction: OUT)
}

type UserDemographics {
    client_id: String,
    type: String,
    value: String,
    users: [User] @relationship(type: "HAS_DEMOGRAPHIC", direction: IN)
    }

image

To Reproduce
Steps to reproduce the behavior:

  1. Start neo4j & graphql server
  2. Execute the following Cypher queries to populate neo4j with sample data:
Create (:User {uid: "user1"}),(:User {uid: "user2"}),(:UserDemographics{type:"Gender",value:"Female"}),(:UserDemographics{type:"Gender",value:"Male"}),(:UserDemographics{type:"Age",value:"50+"}),(:UserDemographics{type:"State",value:"VIC"})
MATCH (u:User {uid: 'user1'}) MATCH (d:UserDemographics {type: 'Gender', value:'Female'}) create (u)-[:HAS_DEMOGRAPHIC]->(d)
MATCH (u:User {uid: 'user2'}) MATCH (d:UserDemographics {type: 'Gender', value:'Male'}) create (u)-[:HAS_DEMOGRAPHIC]->(d)
MATCH (u:User {uid: 'user1'}) MATCH (d:UserDemographics {type: 'Age', value:'50+'}) create (u)-[:HAS_DEMOGRAPHIC]->(d)
MATCH (u:User {uid: 'user2'}) MATCH (d:UserDemographics {type: 'Age', value:'50+'}) create (u)-[:HAS_DEMOGRAPHIC]->(d)
MATCH (u:User {uid: 'user1'}) MATCH (d:UserDemographics {type: 'State', value:'VIC'}) create (u)-[:HAS_DEMOGRAPHIC]->(d)
MATCH (u:User {uid: 'user2'}) MATCH (d:UserDemographics {type: 'State', value:'VIC'}) create (u)-[:HAS_DEMOGRAPHIC]->(d)
  1. Then run the following GraphQL Query:
query{
  users(where: {demographics:{
      type:"Gender", value:"Female"}
  }){
    uid
    demographics{
      type
      value
    }
  }
}
  1. Result returned is:
{
  "data": {
    "users": []
  }
}
  1. Run the following GraphQL Query:
query{
  users(where: {demographics:{
    OR:[
      {type:"Gender", value:"Female"},
      {type:"State"}
      {type:"Age"},
    ]}
  }){
    uid
    demographics{
      type
      value
    }
  }
}
  1. Result returned is:
{
  "data": {
    "users": [
      {
        "uid": "user1",
        "demographics": [
          {
            "type": "State",
            "value": "VIC"
          },
          {
            "type": "Age",
            "value": "50+"
          },
          {
            "type": "Gender",
            "value": "Female"
          }
        ]
      }
    ]
  }
}

Expected behavior
I expected to see the result shown in step 6 to be returned when running the query in step 3. I also expected to see all users listed as a result of the query in step 5 since there is an OR operation

System Details:

Additional context
When running server with Debug Logging DEBUG=@neo4j/graphql:* node src/index.js I can see the generated cypher for the query is using ALL when I replace ALL with ANY and run the same cypher query in neo4j browser, I get the expected result for both queries. Is it done intentionally? From my perspective it doesn't seem intuitive and I don't feel it would be right to list all the possible demographic types when doing the query as this list can change per client and can get updated over time.

Cypher:
MATCH (this:User)
WHERE EXISTS((this)-[:HAS_DEMOGRAPHIC]->(:UserDemographics)) AND ALL(this_demographics IN [(this)-[:HAS_DEMOGRAPHIC]->(this_demographics:UserDemographics) | this_demographics] WHERE this_demographics.type = $this_demographics_type AND this_demographics.value = $this_demographics_value)
RETURN this { .uid, demographics: [ (this)-[:HAS_DEMOGRAPHIC]->(this_demographics:UserDemographics)   | this_demographics { .type, .value } ] } as this
Params:
{
  "this_demographics_type": "Gender",
  "this_demographics_value": "Female"
}

2 instances of DeleteInput type created

I believe I've found a bug in @neo4j/graphql. To reproduce it, see below.

Software

  • Operating system: Debian Linux 10
  • Node.js: version 14.16.1

Files

package.json

{
  "dependencies": {
    "@neo4j/graphql": "1.0.0-beta.2",
    "apollo-server": "2.23.0",
    "graphql": "15.5.0",
    "neo4j-driver": "4.2.3"
  }
}

server.js

'use strict';

const { Neo4jGraphQL } = require('@neo4j/graphql');
const neo4j = require('neo4j-driver');

const driver = neo4j.driver(
  'bolt://localhost:7687',
  neo4j.auth.basic('*****', '*****'),
);

const typeDefs = `
  type Tiger {
    x: Int
  }

  type TigerJawLevel2 {
    id: ID
    part1: TigerJawLevel2Part1 @relationship(type: "REL1", direction: OUT)
  }

  type TigerJawLevel2Part1 {
    id: ID
    tiger: Tiger @relationship(type: "REL2", direction: OUT)
  }
`;

new Neo4jGraphQL({
  driver,
  typeDefs,
});

Setup

  • Run the command npm install --no-save.

Steps to reproduce

  • Run the command node server.js.

Expected outcome

Nothing. That is, the command should exit almost immediately, with no output.

Actual outcome

The following error message:

/app/node_modules/graphql/validation/validate.js:107
    throw new Error(errors.map(function (error) {
    ^

Error: There can be only one type named "TigerJawLevel2Part1DeleteInput".
    at assertValidSDL (/app/node_modules/graphql/validation/validate.js:107:11)
    at Object.buildASTSchema (/app/node_modules/graphql/utilities/buildASTSchema.js:45:34)
    at buildSchemaFromTypeDefinitions (/app/node_modules/@graphql-tools/schema/index.cjs.js:203:26)
    at Object.makeExecutableSchema (/app/node_modules/@graphql-tools/schema/index.cjs.js:749:18)
    at Object.makeAugmentedSchema (/app/node_modules/@neo4j/graphql/dist/schema/make-augmented-schema.js:594:29)
    at new Neo4jGraphQL (/app/node_modules/@neo4j/graphql/dist/classes/Neo4jGraphQL.js:40:44)
    at Object.<anonymous> (/app/server.js:27:1)
    at Module._compile (internal/modules/cjs/loader.js:1063:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Module.load (internal/modules/cjs/loader.js:928:32)

Analysis

TigerJawLevel2Part1DeleteInput, the type that appears in the error message, is obviously a type created by @neo4j/graphql. (See the documentation here.) It appears that @neo4j/graphql creates two instances of this type. That behavior is surely a bug.

Unable to resolve a valid GraphQLSchema

Error

[2021-02-18T07:25:42.150Z] Worker was unable to load function graphql: 'Error: Unexpected error: Unable to resolve a valid GraphQLSchema.  Please file an issue with a reproduction of this error, if possible.'
[2021-02-18T07:25:42.154Z] Worker failed to function id eb16320b-aebf-4b61-b38d-9182e11d37da.
[2021-02-18T07:25:42.164Z] Result: Failure
Exception: Worker was unable to load function graphql: 'Error: Unexpected error: Unable to resolve a valid GraphQLSchema.  Please file an issue with a reproduction of this error, if possible.'
Stack: Error: Unexpected error: Unable to resolve a valid GraphQLSchema.  Please file an issue with a reproduction of this error, if possible.
    at new ApolloServerBase (C:\workPlease\node_modules\apollo-server-core\dist\ApolloServer.js:169:19)
    at new ApolloServer (C:\workPlease\node_modules\apollo-server-azure-functions\dist\ApolloServer.js:16:1)
    at Object.<anonymous> (C:\workPlease\graphql\index.js:38:16)
    at Module._compile (internal/modules/cjs/loader.js:1251:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1272:10)
    at Module.load (internal/modules/cjs/loader.js:1100:32)
    at Function.Module._load (internal/modules/cjs/loader.js:962:14)
    at Module.require (internal/modules/cjs/loader.js:1140:19)
    at require (internal/modules/cjs/helpers.js:75:18)
    at FunctionLoader.load (C:\Program Files\Microsoft\Azure Functions Core Tools\workers\node\worker-bundle.js:20979:22).


  "**dependencies**": {
    "@neo4j/graphql": "^1.0.0-alpha.4",
    "apollo-server-azure-functions": "^2.19.0",
    "dotenv": "^8.2.0",
    "find-duplicate-dependencies": "^2.1.1",
    "graphql": "^15.5.0",
    "jsonwebtoken": "^8.5.1",
    "jwks-rsa": "^1.12.0",
    "neo4j-driver": "^4.2.1"
  }
**Schema**
  type Owner  {   
    id:ID!
  }

requested Server construction

const typeDefs = require("./graphql-schema");
...
const schema = makeAugmentedSchema({ 
  typeDefs
});
...
const server = new ApolloServer(
  { 
    schema, 
    playground:{
      settings:{
        'schema.polling.interval': 90000,
      },
    },
    context: ({ req }) => ({ req, driver })
    
  });

Field must be included in selection set when sorting in a nested selection

Describe the bug

When sorting a nested selection the ordering is not applied if the field used for ordering is not included in the selection set. Sorting works as expected when the field is included in the selection set.

Type definitions

This Codesandbox can be used to demonstrate the issue.

To Reproduce

This query (note that year is not included in the acted_in selection set:

{
  movies(where: { title_CONTAINS: "Matrix Reloaded, The" }) {
    title
    actors(options: { limit: 1 }) {
      name
      acted_in(options: { limit: 3, sort: { year: DESC } }) {
        title
      }
    }
  }
}

produces this result:

{
  "data": {
    "movies": [
      {
        "title": "Matrix Reloaded, The",
        "actors": [
          {
            "name": "Keanu Reeves",
            "acted_in": [
              {
                "title": "Matrix Reloaded, The"
              },
              {
                "title": "Side by Side"
              },
              {
                "title": "Matrix Revolutions, The"
              }
            ]
          }
        ]
      }
    ]
  }
}

however, when year is included in the selection set:

{
  movies(where: { title_CONTAINS: "Matrix Reloaded, The" }) {
    title
    actors(options: { limit: 1 }) {
      name
      acted_in(options: { limit: 3, sort: { year: DESC } }) {
        title
        year
      }
    }
  }
}

then ordering of the acted_in array is properly sorted in the result:

{
  "data": {
    "movies": [
      {
        "title": "Matrix Reloaded, The",
        "actors": [
          {
            "name": "Keanu Reeves",
            "acted_in": [
              {
                "title": "The Neon Demon",
                "year": 2016
              },
              {
                "title": "John Wick",
                "year": 2014
              },
              {
                "title": "47 Ronin",
                "year": 2013
              }
            ]
          }
        ]
      }
    ]
  }
}

Expected behavior

I expect ordering to be applied to the nested field even if the field used to order is not included in the selection set.

System (please complete the following information):

OGM Auto Generated TypeScript

Is your feature request related to a problem? Please describe.
As a user of the OGM, I would like to see autogenerated typescript types. Currently, I have to use generics and cross-check with the GraphQL API.

Describe the solution you'd like
A script I can call to produce typings for the OGM.

Additional context
Please express interest in this issue so we can gauge demand.

Proposal: deep sorting

As explained in neo4j-graphql/neo4j-graphql-java#64 I would suggest to change the way sorting is defined.

Given the Schema:

type Movie {
  title: String
  publishedBy: Publisher @relation(name: "PUBLISHED_BY", direction: OUT)
}

type Publisher {
  name: ID!
}

The augmentation would be:

type Query {
  movie(sort: [_MovieSorting!]): [Movie!]!
}
enum SortDirection{
  asc,
  desc,
}

input _MovieSorting {
  title: SortDirection
  publishedBy: [_PublisherSorting!]
}

input _PublisherSorting {
  name: SortDirection
}

So a user can get all movies ordered by Publisher.name and title by calling

query {
  movie(sort: [{publishedBy: [{name: desc}]}, {title: asc}]) {
    title
  }
}

which results in this query

MATCH (m:Movie)
WITH m
  ORDER BY head([(m)-[:PUBLISHED_BY]->(p:Publisher) | p.name]) DESC, m.name ASC
RETURN m{.title}

This sorting should be possible for all 1..1 relations.

Autogenerated CreateInput has a field of type [String!]!, but it's defined in schema as [String!]

Describe the bug
The autogenerated CreateInput of a type defined in the schema converts a field of type [String!] to [String!]!, making it a required input, even though it should not be.

Type definitions

type Category {
  categoryId: ID! @id
  name: String!
  description: String! @default(value: "")
  exampleImageLocations: [String!]
}

To Reproduce
Steps to reproduce the behavior:

  1. Define a schema with the following type:
type Category {
  categoryId: ID! @id
  name: String!
  description: String! @default(value: "")
  exampleImageLocations: [String!]
}
  1. Execute the following Mutation (I did in GraphQL Playground):
mutation {
  createCategories(
    input: [
      { name: "Category 1"}
      { name: "Category 2", exampleImageLocations: [] }
    ]
  ) {
    categories {
      categoryId
    }
  }
}
  1. See error

Expected behavior
An input value for exampleImageLocations should not be required.

Screenshots
image

System (please complete the following information):

Additional context
N/A

Full-text search

Is your feature request related to a problem? Please describe.
One the features in neo4j-graphql-js which I found mind-blowingly powerful was the super simple way to add support for full-text search, using the @search directive.

Describe the solution you'd like
I would really like a solution similar to the current @search directive, where a directive on the type definition adds the necessary indexes and adds a search-parameter to the generated queries.

Describe alternatives you've considered
Using regex-filters would give the possibility to create a case-insensitive "search". However, it lacks many of the really powerful features of the lucene based search experience, like fuzzy matching.

Proposal: Creating umbrella API-definition project for the java + javascript version

It should be the goal that the API for the java and javascript version are compatible.
That means especially that the used directives and the generated schema are identical or at least compatible.

To achieve this, I propose to set up an umbrella project that will take care of the specification of the API.

This umbrella project will contain the definitions for the directives, documentation and test-cases for the augmented schema.
The TCK for the generated cypher should not be the focus of this project.

I really like the review process we started here, but would like to separate it from @neo4j/graphql.

Make the direction of the @relationship directive an enum

In the examples I saw that the @relationship directive is used with a string value as the directions' argument.
I would suggest to make this field an enum value, so the user knows the possible values.

directive @relationship(type:String!, direction: RelationDirection = OUT) on FIELD_DEFINITION

enum RelationDirection {
  IN
  OUT
}

Sub-graph data isolation for multi-tenancy support

Is your feature request related to a problem? Please describe.
I would like a way to support data isolation across multiple projects in a single database. My concrete use case is to be able to support multiple "tenants" with their own schemas on a single Neo4j Aura instance.

Describe the solution you'd like
In neo4j-graphql-js there is the @additionalLabels directive, which adds the option to inject extra labels on both queries and mutations. Using this I can add a tenant-id as an additional label, which gives me the data isolation I need.

Describe alternatives you've considered

  1. Data isolation using multiple databases
    This is an option that is (mostly) supported in the neo4j-graphql-js library. However, Neo4j aura does not support this
    feature. We have also experienced performance issues with this approach when using a large number of databases.

  2. Data isolation using multiple neo4j aura instances
    This solution is not practically possible today, as there is no way to programmatically manage neo4j aura instances. It will also introduce a lot of extra overhead to have to manage all the instances, and it increases the cost significantly.

$jwt is not available for an authenticated user with a valid JWT

I have an AuthenticatedUser type defined as:

  type AuthenticatedUser {
    userId: ID! @autogenerate
    name: String
    sub: String
  }

I have a sample currentUser query defined which should match the AuthenticatedUser.sub field to the sub field in the decoded JWT:

    """
    A sample query to return details for the authenticated user currently logged in to our system
    """
    currentUser: AuthenticatedUser
      @cypher(
        statement: """
        MATCH (u:AuthenticatedUser {sub: $jwt.sub})
        RETURN u
        """
    ),

When I run this query with a valid JWT in the Authorization HTTP header, the result is:
Screen Shot 2021-01-17 at 22 11 02 PM

Support for federation

Is your feature request related to a problem? Please describe.
neo4j-graphql-js had support for Apollo Federation to build a subgraph, this version has a regression in that regard.

Describe alternatives you've considered
It's impossible to use alternatives besides staying on the old version, as this generates the schema internally instead of exposing generated resolvers.

Int cannot represent non-integer value. Incorrect Int Scalar type.

Describe the bug
Trying to serialize count query I receive: "Int cannot represent non-integer value: { low: 2000, high: 0 }" where 2000 is the expected value.

When running in Neo4J Browser and the equivalent query in GraphQL Architect the count is as expected. This currently appears to just fail when using neo4j/graphql.

Type definitions

import { gql } from 'apollo-server-express'

export const typeDefs = gql`
  type Event @exclude(operations: [CREATE, UPDATE, DELETE]) {
    id: ID! @id
    type: String!
    createdAt: DateTime! @timestamp(operations: [CREATE])
  }

  type TopEventResult @exclude {
    eventType: String!
    eventCount: Int!
  }

  type Query {
    topEvents(): [TopEventResult]!
      @cypher(
        statement: """
        MATCH (e:Event)
        WITH DISTINCT e.type AS eventType, COUNT(*) as eventCount
        RETURN { eventType: eventType, eventCount: eventCount }
        """
      )
  }
`

To Reproduce
Steps to reproduce the behavior:

  1. Run a server with the following code
const app = express()

const startApolloServer = async () => {
  const driver = neo4j.driver(
    NEO4J_URL,
    neo4j.auth.basic(NEO4J_USERNAME, NEO4J_PASSWORD)
  )

  const neoSchema = new Neo4jGraphQL({
    typeDefs,
    resolvers,
    driver,
  })

  const server = new ApolloServer({
    schema: neoSchema.schema,
    context: ({ req }) => ({ req }),
  })

  await server.start()

  server.applyMiddleware({ app })

  await new Promise((resolve: (reason?: any) => void) =>
    app.listen({ port: 4000 }, resolve)
  )

  console.log(
    `๐Ÿš€ Server ready at http://localhost:${PORT}${server.graphqlPath}`
  )

  return { server, app }
}

startApolloServer()
  1. Then run the following Query...
{
  topEvents {
    eventType
    eventCount
  }
}

FWIW: Removing eventCount from the query works.

  1. See error...
{
  "errors": [
    {
      "message": "Int cannot represent non-integer value: { low: 2000, high: 0 }",
      "locations": [
        {
          "line": 8,
          "column": 5
        }
      ],
      "path": [
        "topEvents",
        0,
        "eventCount"
      ],
      "extensions": {
        "code": "INTERNAL_SERVER_ERROR",
        "exception": {
          "message": "Int cannot represent non-integer value: { low: 2000, high: 0 }",
          "stacktrace": [
            "GraphQLError: Int cannot represent non-integer value: { low: 2000, high: 0 }",
            "    at GraphQLScalarType.serializeInt [as serialize] (/var/task/node_modules/graphql/type/scalars.js:49:11)",
            "    at completeLeafValue (/var/task/node_modules/graphql/execution/execute.js:653:37)",
            "    at completeValue (/var/task/node_modules/graphql/execution/execute.js:578:12)",
            "    at completeValue (/var/task/node_modules/graphql/execution/execute.js:556:21)",
            "    at resolveField (/var/task/node_modules/graphql/execution/execute.js:472:19)",
            "    at executeFields (/var/task/node_modules/graphql/execution/execute.js:292:18)",
            "    at collectAndExecuteSubfields (/var/task/node_modules/graphql/execution/execute.js:748:10)",
            "    at completeObjectValue (/var/task/node_modules/graphql/execution/execute.js:738:10)",
            "    at completeValue (/var/task/node_modules/graphql/execution/execute.js:590:12)",
            "    at /var/task/node_modules/graphql/execution/execute.js:620:25"
          ]
        }
      }
    }, ...
}

Expected behavior
Should have returned the count value of 2000 rather than trying to parse { low: 2000, high: 0 }.

System (please complete the following information):

Additional context
@darrellwarde stated on a discussion on Discord in the #graphql channel:

"The only thing that really stands out to me here is that the error is being thrown from the default GraphQL Int scalar, but we have our custom scalar which would be located at /var/task/node_modules/@neo4j/graphql/dist/schema/scalars/Int.js on your machine. So the error itself is valid, the unusual behaviour is that our custom scalar isn't being used... Those are my initial thoughts"

Proposal: new validation directives

A user in slack asked for an ability to enforce the maximum number of returned entities.

To achieve this, I would propose the following new directives:

directive @minValue(value:Float!) on ARGUMENT_DEFINITION | FIELD_DEFINITION | INPUT_FIELD_DEFINITION
directive @maxValue(value:Float!) on ARGUMENT_DEFINITION | FIELD_DEFINITION | INPUT_FIELD_DEFINITION

so it can be used like this:

type User{
  name: String
  size: Float @minValue(value: 20) @maxValue(value: 250)
}

input UserOptions {
  limit: Int @minValue(value: 0) @maxValue(value: 100)
  skip: Int @minValue(value: 0)
}

Contributors

Hi, I would love to help contribute and support this library.

I had some working proposals to add additional features to the GRAND stack like bulk (which seems solved here) and aggregation (cognitedata/neo4j-graphql-js#5).

I would love to help make the graphql integration even better ๐Ÿคฉ

Make @auth directive static

Currently the @auth directive is highly dynamical through itsallow and bind arguments. This usage cannot be used in the java version since the compiled schema will not be valid.

The arguments of a graphql directive must be of known types!

So I would suggest to change the @auth directive to the following format:

# You can put the `@auth` directive also on a field with the `@cypher` directive.
# Functionality like allow and bind will not work but you can still utilize `isAuthenticated` and `roles`.
# Notice you don't need to specify operations for `@auth` directives on `@cypher` fields.
directive @auth(
  # You can have many rules for many operations.
  # We fallthrough each rule, on the corresponding operation, until a match.
  # On no match, an error is thrown. You can think of rules as a big OR.
  rules: [AuthRule!]!
) on FIELD_DEFINITION|OBJECT

input AuthRule{
  # Operations is an array, you can re-use the same rule for many operations.
  operations: [AuthOperations!]
  # This is the most basic of auth. Used to ensure that there is a valid decoded JWT in the request
  isAuthenticated: Boolean
  # Use the roles property to specify the allowed roles for an operation.
  roles: [String!]

  # Use allow to ensure, on matched nodes, a connection exists between a value on the JWT vs a property on each matched node.
  # Allow is used on the following operations:
  #  * read
  #  * update
  #  * connect
  #  * disconnect
  #  * delete
  # When you specify allow on a relationship you can select fields on the referenced node.
  # It's worth pointing out that allow on a relationship will perform an `ANY` on the matched nodes: to see if there is a match.
  # Allow works the same as it does on Type Definitions although its context is the Field.
  # So instead of enforcing auth rules when the node is matched and or upserted, it would instead be called when the Field is selected or upserted.
  allow: AllowValue
  # Use bind to ensure, on creating or updating nodes, a connection exists between a value on the JWT vs a property on a matched node.
  # This validation is done after the operation but inside a transaction.
  # Bind is used on the following operations:
  #  * read
  #  * update
  #  * connect
  #  * disconnect
  #  * delete
  # There may be a reason where you need to traverse across relationships to satisfy your Auth implementation.
  # One example of this could be "Ensure that users only create Posts related to themselves"
  # When you specify `bind` on a relationship you can select fields on the referenced node.
  # It's worth pointing out that allow on a relationship will perform an `ALL` on the matched nodes; to see if there is a match.
  # This means you can only use `bind` to enforce a single relationship to a single node.
  # You can use bind on a field. The root is still considered the node.
  bind: [PathValue!]
  OR: [AuthRule!]
  AND: [AuthRule!]
}

enum AuthOperations {
  # MATCH
  read
  # CREATE
  create
  # SET
  update
  # DELETE
  delete
  # MATCH & MERGE
  connect
  # MATCH & DELETE
  disconnect
  # permit for all operations
  all
}

input AllowValue {
  AND: [PathValue!]
  OR: [PathValue!]
  path: String
  value: String
}

input PathValue {
  path: String!
  value: String!
}

The previous example of:

type User {
    id: ID
    name: String
}

type Post {
    content: String
    moderators: [User] @relationship(type: "MODERATES_POST", direction: "IN")
    creator: User @relationship(type: "HAS_POST", direction: "IN")
}

extend type Post
    @auth(
        rules: [
            {
                operations: ["update"],
                allow: { OR: [{ moderators: { id: "$jwt.sub" } }, { creator: { id: "$jwt.sub" } }] }
            }
        ]
    )

would then be migrated to:

extend type Post
type User {
    id: ID
    name: String
}

type Post {
    content: String
    moderators: [User] @relationship(type: "MODERATES_POST", direction: "IN")
    creator: User @relationship(type: "HAS_POST", direction: "IN")
}

@auth(
  rules: [
    {
      operations: [update],
      allow: {
        OR: [
          { path: "moderators.id", value: "$jwt.sub"}
          { path: "creator.id", value: "$jwt.sub"}
        ]
      }
    }
  ]
)

Note 1
recursion for input arguments for directive arguments seems not to work
so changing:

input AllowValue {
  AND: [PathValue!]
  OR: [PathValue!]
  path: String
  value: String
}

to:

input AllowValue {
  AND: [AllowValue!]
  OR: [AllowValue!]
  path: String
  value: String
}

results in errors.

Note 2
Note that I changed operations to an enum type as well

Unions / Interfaces at top level

As mentioned, unions no longer work at the top level, and interfaces no longer work at the top level or on relationships.

This is a major issue for me, and I hope that we can find a way to maintain this functionality in the new library. I'd like to highlight several use cases that are relevant to me:

1. Unions for Search

Search is the canonical example for unions, and it's not clear how we can replicate this behavior without them.

union SearchResult =
    Album
  | Song
  | Artist
  | UserPlaylist
  | FeaturedPlaylist
  | Podcast

type Query {
  Search(string: String!): [SearchResult!]!
    @cypher(
      statement: """
      CALL db.index.fulltext.queryNodes('names', $string+'~') YIELD node, score
      RETURN node
      """
    )
}

2. Interfaces on Relationships for Activity Feed

Assuming the below schema excerpt, we may want to query for all of a user's activity, ordered by createdAt time

interface Activity {
  id: ID!
  createdAt: _Neo4jDateTime!
  user: User! @relation(name: "BY_USER", direction: "OUT")
}

type AudioListen implements Activity {
  id: ID!
  createdAt: _Neo4jDateTime!
  user: User! @relation(name: "BY_USER", direction: "OUT")
  audio: Audio! @relation(name: "AUDIO_LISTENED", direction: "OUT")
}

type VideoView implements Activity {
  id: ID!
  createdAt: _Neo4jDateTime!
  user: User! @relation(name: "BY_USER", direction: "OUT")
  video: Video! @relation(name: "VIDEO_VIEWED", direction: "OUT")
}

type User {
  id: ID!
  name: String
  createdAt: _Neo4jDateTime
  activity: [Activity!]! 
    @cypher(
      statement: """
        // get all activity, sorted by createdAt field
      """
    )
}

3. Top-Level Interface to Query Across Related Types

Assuming the below schema excerpt, we may wish to query across all Audio types.

interface Audio {
  id: ID!
  name: String!
  url: String!
  listens: [AudioListen!]! @relation(name: "AUDIO_LISTENED", direction: "IN")
}

type Song implements Audio {
  id: ID!
  name: String!
  url: String!
  artists: [Artist!]! @relation(name: "BY_ARTIST", direction: "OUT")
  albums: [Album!]! @relation(name: "HAS_SONG", direction: "IN")
  listens: [AudioListen!]! @relation(name: "AUDIO_LISTENED", direction: "IN")
}

type Podcast implements Audio {
  id: ID!
  name: String!
  url: String!
  host: User!
  listens: [AudioListen!]! @relation(name: "AUDIO_LISTENED", direction: "IN")
}

Each of these works fine with the existing neo4j-graphql-js package. I would advocate strongly for ensuring that these and similar use cases can be easily implemented with the new package.

Fabric support

Is your feature request related to a problem? Please describe.
GraphQL support for Neo4j Fabric mode.

Describe the solution you'd like
It would be great to see an example with this package demonstrating Fabric support.

Describe alternatives you've considered
As I know previous effors on neo4j-graphql-js didn't support Fabric mode.

Auto-generated mutations fail to execute

Hi there! I'm using v1.0.0-alpha.2 and am experiencing an issue with auto-generated mutations erroring out on the @neo4j/graphql side.

I have an example type AuthenticatedUser that is defined as follows:
01-Screen Shot 2021-01-17 at 16 22 01 PM-typeDefs

I see that the library has generated the expected queries and mutations for it:
03-Screen Shot 2021-01-17 at 16 19 04 PM

However, when I try to run the mutation from within GraphIQL, it gives me the following error:
02-Screen Shot 2021-01-17 at 16 23 04 PM

{
  "errors": [
    {
      "message": "Invalid input '{': expected whitespace, comment, namespace of a procedure or a procedure name (line 1, column 6 (offset: 5))\n\"CALL {\"\n      ^",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "createAuthenticatedUsers"
      ],
      "extensions": {
        "code": "INTERNAL_SERVER_ERROR",
        "exception": {
          "code": "Neo.ClientError.Statement.SyntaxError",
          "name": "Neo4jError",
          "stacktrace": [
            "Neo4jError: Invalid input '{': expected whitespace, comment, namespace of a procedure or a procedure name (line 1, column 6 (offset: 5))",
            "\"CALL {\"",
            "      ^",
            "",
            "    at captureStacktrace (/usr/src/app/node_modules/neo4j-driver/lib/result.js:275:15)",
            "    at new Result (/usr/src/app/node_modules/neo4j-driver/lib/result.js:66:19)",
            "    at newCompletedResult (/usr/src/app/node_modules/neo4j-driver/lib/transaction.js:446:10)",
            "    at Object.run (/usr/src/app/node_modules/neo4j-driver/lib/transaction.js:285:14)",
            "    at Transaction.run (/usr/src/app/node_modules/neo4j-driver/lib/transaction.js:121:32)",
            "    at /usr/src/app/node_modules/@neo4j/graphql/dist/cjs/utils/execute.js:64:39",
            "    at TransactionExecutor._safeExecuteTransactionWork (/usr/src/app/node_modules/neo4j-driver/lib/internal/transaction-executor.js:132:22)",
            "    at TransactionExecutor._executeTransactionInsidePromise (/usr/src/app/node_modules/neo4j-driver/lib/internal/transaction-executor.js:120:32)",
            "    at /usr/src/app/node_modules/neo4j-driver/lib/internal/transaction-executor.js:59:15",
            "    at new Promise (<anonymous>)",
            "    at TransactionExecutor.execute (/usr/src/app/node_modules/neo4j-driver/lib/internal/transaction-executor.js:58:14)",
            "    at Session._runTransaction (/usr/src/app/node_modules/neo4j-driver/lib/session.js:300:40)",
            "    at Session.writeTransaction (/usr/src/app/node_modules/neo4j-driver/lib/session.js:293:19)",
            "    at Object.<anonymous> (/usr/src/app/node_modules/@neo4j/graphql/dist/cjs/utils/execute.js:63:104)",
            "    at step (/usr/src/app/node_modules/@neo4j/graphql/dist/cjs/utils/execute.js:33:23)",
            "    at Object.next (/usr/src/app/node_modules/@neo4j/graphql/dist/cjs/utils/execute.js:14:53)"
          ]
        }
      }
    }
  ],
  "data": null
}

Using optimized cypher query when using filtering on relations

Given the schema the schema

type Movie {
    id: ID
    title: String
    actorCount: Int
    genres: [Genre] @relation(name: "IN_GENRE", direction: OUT)
}
type Genre {
  name: String
  movies: [Movie] @relation(name: "IN_GENRE", direction: IN)
}

and the query:

{
    movie(where: { genres: { name: "some genre" } }) {
        actorCount
    }
}

this library is currently generating the following cypher:

MATCH (this:Movie)
WHERE EXISTS((this)-[:IN_GENRE]->(:Genre)) AND ALL(this_genres IN [(this)-[:IN_GENRE]->(this_genres:Genre) | this_genres] WHERE this_genres.name = $this_genres_name)
RETURN this { .actorCount } as this

The java version would instead generate:

MATCH (movie:Movie)
WHERE all(whereMovieGenreCond IN [(movie)-[:IN_GENRE]->(whereMovieGenre:Genre) | whereMovieGenre.name = $whereMovieGenreName] WHERE whereMovieGenreCond) RETURN movie { .actorCount } AS movie

According to @jexp:

Ours [The java version] is better as the planner is able to pull the condition into the expansion and doesn't have to build up a full list and post-filter

see also: neo4j-graphql/neo4j-graphql-java#220 (comment)

btw. in a further step we will migrate to subqueries, to improve the usage of the planner

inferSchema

Hello everyone, I want to know if the function inferSchema() that was available for the previous release neo4j-graphql-js still also available for this release, if yet how can use it?
thanks for your help

Relationship properties

Is your feature request related to a problem? Please describe.
Ability to add properties on relationships

Additional context
Came from here #179

Conflicting configurations of ESLint, Prettier, EditorConfig, etc.

Describe the bug
We have several tools for linting and code formatting, and as it stands they are clashing somewhat.

We need to decide on a standard, configure it uniformly across each tool, and then format the whole codebase to get ourselves on a level playing field across the board.

Neo4jGraphQL contaminates the global scope/and or mergeSchemas

Probably related to #167

Describe the bug
A standalone Apollo Server, stop working if I instantiate Neo4jGraphQL (eg new Neo4jGraphQL(...)) even if the resulting schema is never used. This seems to happen in conjunction with mergeSchemas() - even if no Neo4jGraphQL schema is ever used.

To Reproduce
Consider this example:

import {
  ApolloServer,
  makeExecutableSchema,
  mergeSchemas,
} from 'apollo-server-express';
import express from 'express';
import { Neo4jGraphQL } from '@neo4j/graphql';

const baseSchema = makeExecutableSchema({
  typeDefs: /* GraphQL */ `
    type Query {
      stuff: String
    }

    type Mutation {
      mutateSomething(input: Int!): String!
    }
  `,
  resolvers: {
    Mutation: {
      mutateSomething: () => 'response',
    },
  },
});


// START FAULTY CODE
// NOTE: The Neo4jGraphQL instance is never even used.
// NOTE: If you remove the instantiation of Neo4jGraphQL here `new
// Neo4jGraphQL(...)` the issue disappears
new Neo4jGraphQL({
  typeDefs: /* GraphQL */ `
    interface Thing {
      id: ID!
    }

    type SomeType {
      someField: String!
    }
  `,
});
// END FAULTY CODE

const schema = mergeSchemas({
  // schemas: [baseSchema, neo4jGraphQL.schema],
  schemas: [baseSchema],
});

const port = 4000;
const apolloServer = new ApolloServer({ schema });
const app = express();
apolloServer.applyMiddleware({ app });

app.listen({ port }, () =>
  console.log(
    `๐Ÿš€ Server ready at http://localhost:${port}${apolloServer.graphqlPath}`,
  ),
);

Now run this query:

mutation {
  mutateSomething(input: 1)
}

Which will fail with:

"Variable \"$_v0_input\" got invalid value { low: 1, high: 0 }; Expected type \"Int\". Cannot represent non number as Int"

As you can see, the result of new Neo4jGraphQL() is not even collected in a variable. Still the Apollo server breaks. If you comment out the lines between START FAULTY CODE and END FAULTY CODE, the Apollo server works like a charm and the example query will return:

{
  "data": {
    "mutateSomething": "response"
  }
}

Alternatively you can also just comment out the type definition, to make the vanilla Apollo server happy:

type SomeType {
  someField: String!
}

System (please complete the following information):

"@neo4j/graphql": "1.0.0",
"apollo-server-express": "2.24.0",
"express": "4.17.1",

Why is the mutation result wrapped in an extra type?

for the given schema

type Movie {
    id: ID
}

the following schema will be generated:

// ... the rest

type CreateMoviesMutationResponse {
  movies: [Movie!]!
}

type UpdateMoviesMutationResponse {
  movies: [Movie!]!
}

type Mutation {
  createMovies(input: [MovieCreateInput]!): CreateMoviesMutationResponse!
  deleteMovies(where: MovieWhere): DeleteInfo!
  updateMovies(where: MovieWhere, update: MovieUpdateInput): UpdateMoviesMutationResponse!
}

Is there a reason, why you encapsulate the result of the mutations in a separate type like CreateMoviesMutationResponse or UpdateMoviesMutationResponse and not generate a schema like this:

type Mutation {
  createMovies(input: [MovieCreateInput]!): [Movie!]!
  deleteMovies(where: MovieWhere): DeleteInfo!
  updateMovies(where: MovieWhere, update: MovieUpdateInput): [Movie!]!
}

?

Proposal: Add missing NOT Filter

In the java version we generate an additionally NOT filter allowing to negate an complex filter.

type Movie {
    id: ID
    actorCount: Int
    averageRating: Float
    isActive: Boolean
}

would result in

input _MovieFilter {
  AND: [_MovieFilter!]
  NOT: [_MovieFilter!]
  OR: [_MovieFilter!]
  actorCount: Int
  actorCount_gt: Int
  actorCount_gte: Int
  actorCount_in: [Int]
  actorCount_lt: Int
  actorCount_lte: Int
  actorCount_not: Int
  actorCount_not_in: [Int]
  averageRating: Float
  averageRating_gt: Float
  averageRating_gte: Float
  averageRating_in: [Float]
  averageRating_lt: Float
  averageRating_lte: Float
  averageRating_not: Float
  averageRating_not_in: [Float]
  id: ID
  id_contains: ID
  id_ends_with: ID
  id_gt: ID
  id_gte: ID
  id_in: [ID]
  id_lt: ID
  id_lte: ID
  id_matches: ID
  id_not: ID
  id_not_contains: ID
  id_not_ends_with: ID
  id_not_in: [ID]
  id_not_starts_with: ID
  id_starts_with: ID
  isActive: Boolean
  isActive_not: Boolean
}

Note also that the OR, AND and NOT filters have not nullable items!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.