Rescript bindings for Graphql Codegen

I’ve been looking at Rescript for writing reducer-style code at work, which would require GraphQL support, though in a slightly unusual way. That’s a bit of a wrinkle, since, from playing around with it, graphql-ppx has trouble with certain important features in uncurried mode (for example, fragments). As such, on the side, I’ve been working on binding to graphql-codegen, which has resulted in this monorepo:

Still working on the actual plugin (need to alias keywords, amongst other things), but I’m pretty happy with the bindings to graphql and graphql-codegen, so thought I’d mention it in the forums for posterity.

7 Likes

This is great! Cc @cometkim

Nice showcase of v11 features in the bindings as well.

At some point one could look into leveraging this to get a DX inspired by graphql-ppx: GitHub - zth/rescript-embed-lang: A general purpose PPX and library for embedding other languages into ReScript, via code generation.

2 Likes

Very interesting. Will have to look into that once I hammer the plugin into shape.

1 Like

Feel free to DM me when you’re getting close and we’ll figure it out together. Also feel free to DM (or just post here) about any eventual issues around generating ReScript types for GraphQL. I’ve done that a fair bit myself so I’ve likely hit some if the issues you might face.

Excited about your work!

1 Like

I’m writing a set of graphql-codegen plugin for rescript https://github.com/cometkim/rescript-codegen/tree/main/packages%2Fgraphql-codegen-rescript

After a few more works and fixing a compiler bug, I think it will be better to push to the upstream (graphql-codegen-community), so it should be TypeScript codebase.

However, bindings for graphql-js is good to have, and actually useful a lot!

2 Likes

Yeah, the particular use case I’m thinking of has some unique needs, so I knew I’d have to be writing codegen myself anyways, and decided to play around with the type generation, too. I’d be happy to move off of a self-written plugin for the general types, but hopefully the bindings will be good for per-project plugins, both for myself and others.

1 Like

Had some free time, and realized I never actually added a plugin for generating the base types (enums + input objects) of the schema, so went ahead and remedied that.

1 Like

Managed to get a initial plugin for @zth’s rescript-embed-lang up-and-running on a separate branch. Mostly seems to work, though it feels like I need to tweak the API a bit. Also need to ponder if there’s a way to have shared fragments between ppx snippets.

1 Like

Nice job! :star: Open a PR and ping me and I can give some feedback if you want.

1 Like

Will do, once I’m done tinkering. :wink:

1 Like

I just started to use GraphQL on a project a few days ago—how is this different from using rescript-apollo-client with graphql-ppx?

This repo is for making use of graphql-codegen to generate files. The main benefits are:

  1. Rescript 11 support - this is written in v11, so no worries about uncurried mode.
  2. Transparency - you make graphql files, this generates rescript files. To your average user, the process is much better about ‘showing its work’ without reaching into the guts of ppx.
  3. Customizability - This also provides types to create your own codegen plugins, so if you have a specialized use case, you can write a rescript plugin to generate code for it (that’s the main appeal for me)

This is very much a work in progress, so expect some weirdness if you’re going to try using it now.

4 Likes

Went ahead and polished the repo enough to publish on npm, and added some usage documentation to the repository!

5 Likes

I’ve now made an (admittedly simple) example repository, which uses some additional tweaks I made to bind to and use @apollo/client, along with updating the documentation with some extra usage details!

1 Like

Hi @cwstra ,

First off, brilliant work on the bindings! I cloned the example and tested it with one of our schemas. Everything works smoothly, but I have a question about fragment support.

I attempted to add a simple query with a fragment, and while the types are generated, the generated types appear to duplicate the fragment within the main query type. When I look at the outgoing query in the browser, it doesn’t include the fragment in the query payload.

Here is my example code:

Dummy.graphql

query Dummy($id: ID!) {
  dummy
  getSuit(id: $id) {
    ...SuitFragment
  }
}

fragment SuitFragment on Suit {
  id
  name
}

Which generates:

let gql = GraphqlTag.gql
module SuitFragment = {
  let document = gql`
    fragment SuitFragment on Suit {
      id
      name
      __typename
    }
  `
  type t = {
    id: null<GraphqlBase__Scalars.Int.t>,
    name: null<GraphqlBase__Scalars.String.t>,
  }
}
let document = gql`
  query Dummy($id: ID!) {
    dummy
    getSuit(id: $id) {
      ...SuitFragment
      __typename
    }
  }
`
type variables = {
  id: GraphqlBase__Scalars.Id.t
}
type t_getSuit = {
  id: null<GraphqlBase__Scalars.Int.t>,
  name: null<GraphqlBase__Scalars.String.t>,
}
type t = {
  dummy: null<array<null<GraphqlBase__Scalars.String.t>>>,
  getSuit: null<t_getSuit>,
}

let use = {
  open Apollo.UseQuery
  let res: config<variables, t> => return<'variables, 'result> = useQuery(document, ...)
  res
}
let useLazy = {
  open Apollo.UseLazyQuery
  let res: config<variables, t> => (config<variables, t> => promise<return<variables, t>>, return<variables, t>) = useLazyQuery(document, ...)
  res
}

We have a large schema with many shared fragments, and I would like to use a shared fragment in multiple queries, handling the fragment with a single function. Is this a supported feature?

I really appreciate any help you can provide.

Happy to hear someone’s trying it out!

Since we’re using nominal types, things can get a little messy with fragments. Because they can be combined with individual fields or other fragments, the current code cuts them all up into the individual field selections (recursively), and recombines them; do let me know if you find a bug in that process, by the way!

I could (and probably should) special-case this particular instance (a fragment spread on its lonesome), since it’s the one time where I can just use fragments out of the box, but until I do that, there’s another option: if all’s well, the type coming out of document should be compatible with the fragment’s type, which means we can use the type coercion operator :> to cast it, e.g.

let dummyToSuitFragment = (t: Dummy.t) => (t.getSuit :> Dummy.SuitFragment.t)

This might start to break down if you have union spreads getting into the mix, but hey! Baby steps.

1 Like

I work with tdfairbrother and was pairing with him when we tried this out. It compiles fine, it just breaks on the query call with Invariant violation fragment not found.

We do have a query comprised of a big union leveraging fragments per type. Again compilation seems fine but a similar issue when we query. As tdfairbrother mentioned the query doesn’t include the fragment in the query document.

I like how you suggested leveraging coercion. Useful once we can get a successful query response from fragments.

We’re super keen on your work here given some stagnation and delays in other GraphQL binding libraries. We’re very wedded to GraphQL and our paths to upgrading our large code base to Rescript 11 are impeded by this and MUI date pickers.

Cheers

Well, the bad news is, my eyes completely skipped over the runtime issue in @tdfairbrother’s initial post; the good news is I noticed the issue when I was comparing outputs to typescript plugins. :slight_smile:
New versions of the following packages have been published:

  • @rescript-graphql-codegen/lib
  • @rescript-graphql-codegen/graphql-tag
  • @rescript-graphql-codegen/operations

Bumping operations should fix both issues. If using a custom implementation of graphql-tag, see the wiki for the new fragmentWrapper config parameter.

2 Likes