ra-data-prisma
ra-data-prisma copied to clipboard
Allow fragment spread in resource views
Currently, we can specify arbitrary selection on a given resource, if we write it as a fragment on that given resource. Fields selected in this fragment are then used to build the query that is sent to the server.
The goal is to take
fragment X on Y {
field
anotherField
}
and produce a query
query Ys($where: YWhereInput $orderBy: [YOrderByInput!] $take: Int $skip: Int) {
items: Ys(where: $where orderBy: $orderBy take: $take skip: $skip) {
field
anotherField
}
total: YsCount(where: $where)
}
This works well. I personally had a situation where I wanted to extract fields that are common in one
and many
fragments so I don't forget anything, like this:
const common = `
id
field
`
const one = gql`
fragment Xone on Y {
${common}
anotherField
}
`
const many = gql`
fragment Xmany on Y {
${common}
}
`
This also works well, and allowed me to deconstruct the fragments into logical parts.
But then I ran into a problem. I wanted to have the types of the queries generated. In our case we use @graphql-codegen
library which can scan files for export const something = gql....
objects and parses them and generates static types for them. The problem is, it needs to have everything available on compile time, so it can't process the previous example (explanation here). On the other hand, codegen
can process GraphQL fragments, so if we were to rewrite the example with that, it would work:
const common = gql`
fragment Xcommon on Y {
id
field
}
`
const one = gql`
fragment Xone on Y {
...Xcommon
anotherField
}
${common}
`
const many = gql`
fragment Xmany on Y {
...Xcommon
}
${common}
`
But then if we want to use one
and many
in our resource views, we run into a problem with the way we process the fragment. This is because one
contains in this case
fragment Xone on Y {
...Xcommon
anotherField
}
fragment Xcommon on Y {
id
field
}
which in code is a DocumentNode
but with 2 definitions
, and ra-data-prisma
uses the first one, which would result in a spread of unknown fragment. AFAIK there's no way to combine the two approaches without duplication e.g.
At first I thought we could try to do the fragment "interpolation" manually (after all, the document is a JS object) but then again, we could miss something if there were many fragments combined. On a second thought though, we are in control of the outgoing query to the server, and if we were to supply all the used fragments outside of the query
, it produces a valid GQL operation:
fragment X on Y {
field
anotherField
}
query Ys($where: YWhereInput $orderBy: [YOrderByInput!] $take: Int $skip: Int) {
items: Ys(where: $where orderBy: $orderBy take: $take skip: $skip) {
...X
}
total: YsCount(where: $where)
}
This would keep the functionality intact if I'm not missing something, and allows us to freely use GQL fragments and in my case, the @graphql-codegen
library.
Footnote: Not using fragments and directly specifying every field from the beginning works for both as well, but I think being able to use fragments would be nicer and more flexible.
i agree that it should work with fragments too that use other fragments.
what's the solution you would propopse?
Looking at the code and the example I found works (putting the fragments before query), I think what could work is:
- extract the fragment definitions from the document (I guess somehow filter the
definitions
on theDocumentNode
) - putting them inside the generated document before the
query
operation (because document accepts an array) - change the
buildFieldsFromFragment
somehow to use the right fragment for it
Thinking about it, the last thing might be tricky, because in the input, all of them are fragments, and can be even on the same resource.. maybe we would have to enforce some kind of rule to make it work? Like if we would require that the fragment applied to the query was first (like
gql`
fragment Xmany on Y {
...Xcommon
}
${common}
`
then we know the first fragment is the one we should apply and the rest should be put in front of the query.
I can try to implement that as well and see if it works but unfortunately I don't have time for it right now so just wanted to get some feedback first :)
EDIT: Or we might be able to separate the "helper" fragments into a different property on the ResourceView
, separately from the "main" fragment, but frankly while that would help us in the code, I can't think of any elegant way to name it so it wouldn't be obnoxious to write 😅 It just seems much easier to write directly into the used fragment.