Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow fragment spread in resource views #55

Open
sMteX opened this issue Mar 6, 2021 · 2 comments
Open

Allow fragment spread in resource views #55

sMteX opened this issue Mar 6, 2021 · 2 comments

Comments

@sMteX
Copy link
Contributor

sMteX commented Mar 6, 2021

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.

@macrozone
Copy link
Member

i agree that it should work with fragments too that use other fragments.

what's the solution you would propopse?

@sMteX
Copy link
Contributor Author

sMteX commented Mar 8, 2021

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 the DocumentNode)
  • 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants