rescript-relay icon indicating copy to clipboard operation
rescript-relay copied to clipboard

Sharing generated types across modules

Open mbirkegaard opened this issue 2 years ago • 2 comments

As it is now, the generated types are unique to each generated module, even when they correspond to the same type from the schema.

To illustrate, consider this (very contrived!) schema

type ComplexNumber {
  real: Float!
  img: Float!
}

input ComplexNumberInput {
  real: Float!
  img: Float!
}

input AddInput {
  a: ComplexNumberInput!
  b: ComplexNumberInput!
}

input ScaleInput {
  a: Int!
  b: ComplexNumberInput!
}

extend type Mutation {
  add(input: AddInput!): ComplexNumber!
  scale(input: ScaleInput!): ComplexNumber!
}

When using these mutations in the frontend, separate modules are generated for each mutation, each with their own type definitions. It would look something like this for the add mutation

module Types = {
  @live
  type rec addInput = {
    a: complexNumberInput,
    b: complexNumberInput,
  }
  @live
  and complexNumberInput = {
    real: float,
    img: float,
  }
}

module Utils = {
  let make_complexNumberInput: (~real: float, ~img: float) => complexNumberInput = ""
}

and similar for the scale mutation

module Types = {
  @live
  type rec scaleInput = {
    a: int,
    b: complexNumberInput,
  }
  @live
  and complexNumberInput = {
    real: float,
    img: float,
  }
}

module Utils = {
  let make_complexNumberInput: (~real: float, ~img: float) => complexNumberInput = ""
}

Since the two mutation modules have their own definitions of complexNumberInput it's not possible to share helpers or converters or whatever that are intended to produce ComplexNumberInputs... (I might have a function that produces random numbers of my own internal type ComplexNumber.t, for instance). I need two different converters from ComplexNumber.t to each of the input types, even though they both correspond to ComplexNumberInput from the schema.

Would it be possible (and would it be desirable) to generate a ComplexNumberInput_graphql.res module and use this instead? That module would be something like

type complexNumberInput = {
  real: float,
  img: float,
}

module Utils = {
  let make_complexNumberInput: (~real: float, ~img: float) => complexNumberInput = ""
}

The two mutation modules would then use the definitions from ComplexNumberInput_graphql (or include ComplexNumberInput_graphql if there's a reason to get the code into the compiled bs.js).

mbirkegaard avatar May 17 '22 11:05 mbirkegaard

I definitively agree this is something we need to solve somehow. I'd say the issue primarily lies in the following:

  • The Relay compiler has no concept of outputting files for single types. It's of course doable to implement, but one would have to weigh the cost/benefits wrt maintenance etc.
  • Input objects in GraphQL can be (mutually) recursive, so all of the type definitions will need to go into the same file.

A few ideas for solutions:

  • Extend the compiler to output a schema assets file of some sort, which would print types for all input objects. This is good long term because we could also stick enums in there eventually, which would also be good to share. And potentially also utils for input objects/enums. However, we'd need to be very careful so that everything is tree shakeable.
  • Make it possible to somehow configure input objects to be modelled as structural instead of nominal. Would solve the issue, but at the cost of worse error messages, and some general confusion.

I lean towards 1), but I'd need to explore how much (and how invasive) work it is. I can also talk to the core team whether a "post build generate hook with access to the full schema" is something that they'd be interested in centrally.

zth avatar May 19 '22 05:05 zth

I definitively agree this is something we need to solve somehow. I'd say the issue primarily lies in the following:

* The Relay compiler has no concept of outputting files for single types. It's of course doable to implement, but one would have to weigh the cost/benefits wrt maintenance etc.

I'd like to understand this more. Could the ppx "somehow" generate the schema assets and then open the schema assets file in MyComponentMyOperation_graphql.res? As long as MyComponentMyOperation_graphql.bs.js has what is needed, isn't the relay compiler going to be okay? (Caveat... I have no idea what I'm talking about 😅)

EDIT: Ah, no I think I understand the problem. The ppx uses the relay compiler to output the types?

* Input objects in GraphQL can be (mutually) recursive, so _all_ of the type definitions will need to go into the same file.

Oh, right! I completely did not consider that.

A few ideas for solutions:

* Extend the compiler to output a schema assets file of some sort, which would print types for _all_ input objects. This is good long term because we could also stick enums in there eventually, which would also be good to share. And potentially also utils for input objects/enums. However, we'd need to be very careful so that everything is tree shakeable.

* Make it possible to somehow configure input objects to be modelled as structural instead of nominal. Would solve the issue, but at the cost of worse error messages, and some general confusion.

I lean towards 1), but I'd need to explore how much (and how invasive) work it is. I can also talk to the core team whether a "post build generate hook with access to the full schema" is something that they'd be interested in centrally.

I also think 1) sounds better. Re tree-shakeability: All type information dissappears when .res files are compiled to .bs.js, right? If only type definitions are in the schema assets file, then it shouldn't affect final bundle-size?

mbirkegaard avatar May 20 '22 12:05 mbirkegaard

This is going to receive some attention soon.

zth avatar Aug 26 '22 06:08 zth