Coobaha / graphql-to-reason

transform GraphQL schema to ReasonML types
MIT License
52 stars 3 forks source link

Types compatible with Reason native (ie. sans BuckleScript) #5

Open mscharley opened 5 years ago

mscharley commented 5 years ago

Feature Request

Is your feature request related to a problem? Please describe. I'm trying to use graphql-to-reason to generate types from a schema SDL file which can be used by my server (ocaml-graphql based native project) and my frontend project (ReasonReact + reason-apollo).

Kudos for a light-weight way to get going. Generating types from the SDL file was simple to get going. However the currently generated types rely on BuckleScript, so I'm unable to use them on my backend project without manually patching them.

Describe the solution you'd like A clear and concise description of what you want to happen. Add any considered drawbacks.

BuckleScript makes the most sense for frontend JS applications. For native though, you'd want to use record syntax rather than Js.t.

The simplest way I can think of to trigger between the two syntaxes is either to have a --native flag or a --syntax=[bs|native] option. The second option is more future proof, but presently I can't think of a possible third syntax option.

I'll report back with what changes I've needed to make to get a simple example server working when I get a simple example server working.

Describe alternatives you've considered None, though bs-native seems like an interesting option. That was my first attempt when spinning up this project but esy + bs-native is much harder to get going than esy + pesy + dune from my personal experience with one project to the point where I gave up on it. It'd be nice to support non-bucklescript projects any way, in my opinion.

Teachability, Documentation, Adoption, Migration Strategy

This would be a secondary generation method which is completely opt-in. It would not affect current users.

mscharley commented 5 years ago

On second thoughts, I don't think this is a good idea for ocaml-graphql specifically (because of their API) so I might not need this personally. It's still an interesting idea though so I'll leave this issue open for now. Feel free to close it though if you'd like.

Coobaha commented 5 years ago

Hi @mscharley Thanks for your very detailed future request! I think this is doable but we need to come-up with correct native types or even different approach of code generation.

[@bs.deriving {jsConverter: newType}] is used for graphql enums Js.Nullable.t is used for graphql nullable type [@bs.deriving abstract] is used to create resolvers object with all optional resolvers and bs.as

I will try to investigate this when i will have time for this. I am not very familiar with native ocaml development but seems that ocaml-graphql is the only way to use graphql? So maybe instead of types - runtime code can be generated that is accepting resolvers and outputting ocaml-graphql code?

PS. package name would need to be changed then xD

mscharley commented 5 years ago

Yeah, about the only way that I can see this working with ocaml-graphql is if you generated the entire integration with that library. It really wants to manage the definition of types dynamically through building up the schema with their DSL. I was originally imagining something very light touch, swapping Js.t objects for records, etc. If you did want to support this though, I'd recommend using the --syntax=ocaml-graphql style option though since you could imagine supporting multiple libraries.

For the sake of giving you an example though:

enum Role {
  USER,
  ADMIN
}

type User {
  id: Int!
  name: String!
  role: Role!
  friends: [User!]
}

type Query {
  users: [User!]!
}

schema {
  query: Query
}

becomes:

open Lwt;
open Graphql_lwt;

type context = unit;

type role =
  | User
  | Admin;
type user = {
  id: int,
  name: string,
  role,
  friends: list(user),
};

let rec alice = {id: 1, name: "Alice", role: Admin, friends: [bob]}
and bob = {id: 2, name: "Bob", role: User, friends: [alice]};

let users = [alice, bob];

let role =
  Schema.(
    enum(
      "role",
      ~values=[
        enum_value("USER", ~value=User, ~doc="A regular user"),
        enum_value("ADMIN", ~value=Admin, ~doc="An admin user"),
      ],
    )
  );

let user =
  Schema.(
    obj("user", ~fields=user =>
      [
        field("id", ~args=Arg.[], ~typ=non_null(int), ~resolve=((), p) => p.id),
        field("name", ~args=Arg.[], ~typ=non_null(string), ~resolve=((), p) => p.name),
        field("role", ~args=Arg.[], ~typ=non_null(role), ~resolve=((), p) => p.role),
        field("friends", ~args=Arg.[], ~typ=list(non_null(user)), ~resolve=((), p) => Some(p.friends)),
      ]
    )
  );

let schema =
  Schema.(
    schema([
      field(
        "users",
        ~typ=non_null(list(non_null(user))),
        ~args=Arg.[],
        ~resolve=(_, _) => users
      ),
    ])
  );
Coobaha commented 5 years ago

I was originally imagining something very light touch, swapping Js.t objects for records, etc.

@mscharley So how this can work? Any example of such server? GraphQL server will respond with JSON version of record representation?

(I am asking because ocaml-graphql-server is different, one need to write resolvers for every field there)

mscharley commented 5 years ago

ocaml-graphql-server does typically use records for object types. As you point out though, it doesn't auto-implement any resolvers for you.

The biggest issue with the current version of the types and using them with ocaml-graphql-server is there's no way to apply the resolver types to the resolver object constructions that ocaml-graphql-server uses.

I could imagine a solution with Functors, but it would be quite different to the current set of types and the specific implementation hasn't quite solidified in my mind yet.

idkjs commented 5 years ago

Maybe taking a look at https://github.com/zth/reason-relay will get some ideas on moving this idea forward. I love this project even if I dont fully understand writing resolvers with it yet.