I want to create a model (let’s call it Link) with a polymorphic reference any model, i.e. has a link: StreamID! field. With this, it’s easy to pick up these links on any target model with a @relationFrom(model: "Link", property: "link") directive.
What I’m wondering is if it’s possible to follow the outgoing relation on Link somehow?
A little code example here to illustrate what I mean:
type Link
@createModel(accountRelation: LIST, description: "Link to things")
{
source: DID! @documentAccount
targetID: StreamID!
# not possible because no generic type exists
target: Any! @relationDocument(property: "targetID")
# maybe possible with union support, but needs to be changed every time a
# new type of target needs to be linked
target2: (Union Target = A | B | C)! @relationDocument(property: "targetID")
}
type MyType
@createModel(accountRelation: LIST, description: "A thing"
{
# easy peasy
incomingLinks: [Link] @relationFrom(model: "Link", property: "targetID")
}
Below is an example of where the query stops short. Since the targetID field in the linkIndex query is typed StreamID there is no type to traverse into (no known fields). One can re-use the resulting streamID in a second round-trip in a node(id: xyz) { ...on MyType } and match it on the expected type, or try them all and see what returns. Is there no way of combining these?
# Is there a way of connecting the lower node() query with the upper one
# to continue traversal?
query {
linkIndex(first: 10) {
edges {
node {
targetID
}
}
}
node(id: "TARGET ID GOES HERE") {
... on MyType {
title
}
...on OtherType {
cid
}
}
So,
a) Is it impossible to traverse a StreamID typed field?
b) If so, is there workaround (without having to do additional round trips) to resolve polymorphic links?
As you wrote, unions would probably not be a great fit for this because they would be static at the time the model is created.
One project we have in the backlog is to support interfaces, which would act as sort of abstract models that can’t have documents directly associated to them, but can define fields that need to be supported by models implementing the interface, this could look something like this:
type Link @createModel(...) {
targetID: StreamID!
# the target here uses the Node interface that can represent any uniquely identified entity (account or document) supported by ComposeDB
target: Node @relationDocument(property: "targetID")
# ... other fields
}
# All documents implicitly implement the Node interface
type MyType @createModel(...) {
incomingLinks: [Node] @relationFrom(model: "Link", property: "targetID")
# ... other fields
}
Unlike unions, the advantage here is that you could add other models over time that would be implicitly supported by the Link type.
Here Node is a special interface because it’s already supported in some ways by ComposeDB, our project to support interfaces isn’t specifically made to support this use-case, but if you think that could be a solution for you I think this case could be handled as part of the larger project?
I’m a bit confused regarding the special-case Node interface in the example, is it already supported when creating models or are you just using it as an example? Because the schema compiler isn’t having it
Just a heads-up, I tried out this pull request that @paul merged recently – it works super, exactly as described above. It will allow us to simplify our architecture and save a ton of time. Here’s a screenshot showing it in action. Big thanks