neo4j / graphql

A GraphQL to Cypher query execution layer for Neo4j and JavaScript GraphQL implementations.
https://neo4j.com/docs/graphql-manual/current/
Apache License 2.0
496 stars 148 forks source link

Ability to execute custom Cypher fields in root `Query` and `Mutation` from OGM #843

Open darrellwarde opened 2 years ago

darrellwarde commented 2 years ago

Is your feature request related to a problem? Please describe.

Users may have custom Cypher fields in their root Query and Mutation types which return object types represented by models in their OGM instances. It would be great if they could execute these and have the results returned in OGM model instances.

Describe the solution you'd like

A mechanism for executing root Query and Mutation fields from the OGM.

This could be either:

  1. The root Query and Mutation types are scanned for fields returning types matching OGM models, and these fields are somehow added to the model instances.
  2. We expose something like OGM.Query or OGM.model("Query") which allows you to execute against fields in these root types. Perhaps each function within them could accept a generic model, so for a root Query field test that returns a User model you could execute something like const Query = OGM.model("Query"); const User = OGM.model("User"); const result = Query.test<User>();

It should be possible to combine the @private directive with these custom Cypher fields so that particular fields can be made available to the OGM only.

Additional context

A branch off of #716 which feels like a more moderate solution.

darrellwarde commented 2 years ago

Approach 2: Extend Neo4jGraphQL

Implementation: dmoree@cada48f

This is interesting, but again, why not just use the @cypher directive?

I'm thinking perhaps that a better approach would be to just call a graphql function that would run already generated @cypher resolvers (just like the other find, create, update, or delete methods). It could be expressed like this instead:

  type Query {
    currentUser(id: ID!): User!
      @cypher ( 
          statement:
            """
              MATCH (u:User{id:$id})
              RETURN u
            """
          )
  }
const user = await User.query({
    field: "currentUser"
    selectionSet: `
        {
            name
            posts {
                title
                comments {
                    comment
                }
            }
        }
    `,
});

Or even

const user = await User.currentUser({
    selectionSet: `
        {
            name
            posts {
                title
                comments {
                    comment
                }
            }
        }
    `,
});

Though I don't know if it's possible to dynamically create methods based on the schema.

Moving this comment from @maieulChevalier into here as a better example of how option 1 might look.

Let's continue this discussion here as #716 covers far too many bases.

maiieul commented 2 years ago

From @dmoree:

An attempt at Model.query here: dmoree@1a478e9

Test here: dmoree@334795d

maiieul commented 2 years ago
const user = await User.currentUser({
    selectionSet: `
        {
            name
            posts {
                title
                comments {
                    comment
                }
            }
        }
    `,
});

Actually there could be a Mutation field and a Query field with the same name, so I guess this isn't valid.

dmoree commented 2 years ago

I guess one could have a query and a mutation with the same name, but that probably wouldn't be advised. Even so, a field like operation that takes "Query" | "Mutation" could work around the ambiguity.

maiieul commented 2 years ago

Yeah you're probably right.

Yet I have the feeling that the most intuitive approach for users would be the second proposition from darrell:

2. const Query = OGM.model("Query"); const User = OGM.model("User"); const result = Query.test();

Isn't it a bit weird to write const user = await User.query({...?

darrellwarde commented 2 years ago

Isn't it a bit weird to write const user = await User.query({...?

To me, query is just another word for find, so it could be misconstrued that it just finds a user or users.

As I was writing option 2 it felt a bit better - having "special objects" for Query and Mutation, much like they are "special objects" also in GraphQL.

aibps commented 1 year ago

Hello, when will this feature be ready?