sinclairzx81 / typebox

Json Schema Type Builder with Static Type Resolution for TypeScript
Other
5.03k stars 158 forks source link

Possibility to compile to graphql (input)types #224

Closed ilijaNL closed 2 years ago

ilijaNL commented 2 years ago

This is a really great project and I use it every day! Might be a little bit offtopic but I was wondering if it is possible to compile to graphql types and input types from typebox objects since they are somewhere in between json schema and typescript types. If you have any idea and guidelines I am willing to contribute since this can make writing graphql mutation a lot easier.

sinclairzx81 commented 2 years ago

@ilijaNL Hi, TypeBox was actually written with this use case in mind :) All the current TypeBox types are reflectable and can be used to generate other type representations (including GraphQL).

const T = Type.Object({
  x: Type.Number(),
  y: Type.Number()
})

// Generate GQL Types from Schematics
T.type             // 'object'
T.properties.x     // { type: 'number' }
T.properties.y     // { type: 'number' }
T.required         // ['x', 'y']

Additionally, the TypeBox Type.Function(...) and Type.Constructor(...) types are provided allow you to frame callable functions, which could be used to map onto GQL mutations and queries. The following is a rough Idea.

// -------------------------------------------------------------------------
// Define GQL Mutation and Query Contract
// -------------------------------------------------------------------------

const CreateUserRequest = Type.Object({ ... })
const CreateUserResponse = Type.Object({ ... })

const GQLContract = Type.GQLContract({
  mutations: {
     createUser: Type.Function([CreateUserRequest], CreateUserResponse)
  }
})

// -------------------------------------------------------------------------
// Pass GQLContract to API to be implemented
// -------------------------------------------------------------------------

const graphql = new GraphQLTypeBox(GQLContract)

graphql.mutation('createUser', (context, request) => {
   return { ... } //                      ^ auto inferred as CreateUserRequest 
})

If you were interested in exploring a GQL implementation, I would be happy to provide guidance on that. Also for reference, I currently I manage https://github.com/sinclairzx81/sidewinder which uses a similar design to the above (but is built for JSON RPC 2.0). Sidewinder is the intended design for more advanced integrations of TypeBox, so can be used as a reference point for a potential GQL project.

You can experiment with the link below.

TypeScript Link Here

Happy to discuss more, but hope the above provides some initial ideas on how one could approach a GQL implementation. Cheers! S

ilijaNL commented 2 years ago

Thanks @sinclairzx81 for your quick reaction. I checked the sidewinder project. I really like the idea to have a contract defined as a json schema with types, which then can be used in typescript and non typescript environments. An additional benefit of using a json contract is that is serializable, thus can generate code on other platforms by introspecting the contract via http, and is similar to graphql's SDL/introspection query. However, for small projects, I like to define the "contracts" in a code first approach, let me explain what I mean by that in code, using your snippet from issue https://github.com/trpc/trpc/issues/2453:

Typscript playground

Especially inspect the bottom of the snippet:

// returns a valid GraphqlSchema that can be used by any graphql server package
const graphqlSchema = createGraphqlSchema([routerDefinition], () => ({}));
//  returns a valid fastify plugin that can be used with fastify.register()
const fastifyPlugin = createFastifyPlugin([routerDefinition], () => ({}));
// any other framework
const expressRouter = createExpressRouter([routerDefinition],() => ({}));

As you can see this is similar to TRPC, however I think TRPC is doing to much things out of the box. Back to graphql. I think the hardest thing to do is to convert from jsonschema to graphql SDL. There are some open source packages like https://github.com/lifeomic/json-schema-to-graphql-types but the seems rather to be limited. I wonder if typebox provides any additional information which can be used to generate a valid graphql SDL.

Perhaps it is better to get in touch online to discuss this further.

Thank you

sinclairzx81 commented 2 years ago

@ilijaNL See https://replit.com/@sinclairzx81/GraphQLCompiler#index.ts for an example of compiling TypeBox types into GraphQL IDL. This implementation is a quick draft, but you should be able to take what's here and extend as you need (just copy and paste from the repl.it environment).

As for integration into server infrastructure, it should be possible to map the return type of Type.Contract(...) over tRPC, Fastify or Sidewinder. You can use Static<...> to extract to parameters and return types from the Type.Function() definitions for inference when doing that integration.

Hope this helps! S

ilijaNL commented 2 years ago

Thank you very much, you are a legend. I have last question: how does typescript perform when having a large contract with sidewinder? Say 500+ queries/mutations? Does it have the same issues as trpc when having a lot queries/mutations when inferring the client?

sinclairzx81 commented 2 years ago

how does typescript perform when having a large contract with sidewinder? Say 500+ queries/mutations? Does it have the same issues as trpc when having a lot queries/mutations when inferring the client?

Ah, I don't have exact benchmarks for type inference, but I do currently use Sidewinder for some very large microservice backends with dozens of contracts (with hundreds of functions, and many more types). In my experience, the type inference performance is quite fast (even for recursive types). I should probably push the framework a bit more.

I can't really speak for tRPC as I've only briefly looked at it when someone asked me about TypeBox integration. Based on the comment from KATT on https://github.com/trpc/trpc/issues/2453, it seems like Ajv or TypeBox is not going to be a priority for them. I did briefly draft an updated trpc API for them to be supportive of both Zod and TypeBox using a type plugin (HKT) infrastructure here, but I'm doubtful they will adopt this design.

Might close off this issue for now, if you wanted to setup a repository for the GQL stuff, feel free to ping me over there. May be able to help with a GQL initiative down the road (maybe helping out with the inference if you need)

Cheers! S

ilijaNL commented 2 years ago

Thank you for your guidelines en help. Let's see if I can come up with a better alternative to type-graphql with help of typebox

teague2 commented 1 year ago

Any updates on this?