Reusing Custom Cypher Logic Between all instances of an Interface

I'm using the neo4j-graphql-js library to interact with my Neo4j database using a GraphQL API - an issue that keeps cropping up is being forced to re-define custom cypher logic in all instances of an interface.

For example:

interface Base {
  field: String!
}

type DerivedA {
  field: String! @cypher(statement: """ my long cypher query """)
}

type DerivedB {
  field: String! @cypher(statement: """ my long cypher query """)
}

Where the implementation of field is exactly the same in all types derived from the interface. Is there a way I can re-use this code without copy and pasting it everywhere?

Welcome to the forums Jamie. I think it should be possible to include the @cyper query in the root of the interface, so that you would only have to declare it once. Have you tried that?

@MuddyBootsCode - trying to replicate my issue using a minimal code example I now realize that what you describe above does work for trivial examples, but breaks in my actual code base. Should have tried this earlier! Stripping my codebase down, I've create an example which still triggers what I believe is a bug in neo4j-graphql-js:

This is my schema:


# An interaction between two sets of entities
type Interaction {
	kind: String! # Type of interaction that occured, eg, visited, spoke to, etc

	subjects: [Entity] @cypher(statement: """
		MATCH(this:Interaction)<-[:PARTICIPATED_IN { subject: true }]-(e:Entity)
		RETURN e
	""")

	objects: [Entity] @cypher(statement: """
		MATCH(this:Interaction)<-[:PARTICIPATED_IN { subject: false }]-(e:Entity)
		RETURN e
	""")

	participants: [Entity] @cypher(statement: """
		MATCH(this:Interaction)<-[:PARTICIPATED_IN]-(e:Entity)
		RETURN e
	""")

    # ... extra fields for time, location, etc
}

interface Entity {
	name: String!

	subjectIn: [Interaction] @cypher(statement: """
		MATCH(this:Entity)-[:PARTICIPATED_IN { subject: true }]->(i:Interaction)
		RETURN i
	""")

	objectIn: [Interaction] @cypher(statement: """
		MATCH(this:Entity)-[:PARTICIPATED_IN { subject: false }]->(i:Interaction)
		RETURN i
	""")

	participantIn: [Interaction] @cypher(statement: """
		MATCH(this:Entity)-[:PARTICIPATED_IN]->(i:Interaction)
		RETURN i
	""")
}

type Person implements Entity {
	name: String!
	# ... other Person fields ...
}

type Place implements Entity {
	name: String!
	# ... other Person fields ...
}

If I seed the DB with this data:

MERGE(a:Person:Entity { name: "Bob"})
MERGE(b:Place:Entity  { name: "England"})
MERGE(i:Interaction { kind: "visited"})
MERGE (a)-[:PARTICIPATED_IN { subject: true  }]->(i)
MERGE (b)-[:PARTICIPATED_IN { subject: false }]->(i)
RETURN *;

The following query fails:

query {
  Person {
    name
    subjectIn {
      kind
      objects {
        name
      }
    }
  }
}

With the error:

"Variable `undefined` not defined (line 1, column 44 (offset: 43))\n\"MATCH (`person`:`Person`) RETURN `person` {undefined} AS `person`\"\n     

I actually wonder if this is another intance of a bug I (and others) have reported on github:

Do you think my above GraphQL schema and query should work? (in which case this is a bug in neo4j-graphql-js, or am I missunderstanding something about how this should work?

It's possible, though one things that jumps out is that normally when specifying your cyper query you would omit the type, so something like:

MATCH (this)

vs.

MATCH (this:Entity) 

It may be worth a try otherwise I would think that it's a bug somewhere in the code for sure. Also, I know this was in your first post but it may just require doing copy pasta of the field definition for the implementation types.

Removing the type from the Cypher query didn't make any difference.

Copy and pasting the query to all derived types fixes the error, its just a pain to keep them all in sync when there are many more than just 2 shown here.

Thanks for the help.

I totally understand, there are some aspects of this that are just pain and the library hasn't smoothed it out much.