-
Notifications
You must be signed in to change notification settings - Fork 7
Using binding-ts for defining resolvers #72
Comments
Alright, I've managed to create my own generator and it's working nicely. I've also tweaked enum generation to have a real TS enum instead of a union. I am not even generating root schema interface as I don't see a reason for that. It's rather crude and hard-coded, mostly because I could not figure how to add this to import * as path from 'path'
import * as fs from 'fs-extra'
import { GraphQLObjectType, isNonNullType } from 'graphql'
import { generateCode } from 'graphql-static-binding'
import {
generator as gcgenerator,
renderFieldName,
renderFieldType,
} from 'graphql-static-binding/dist/generators/graphcool-ts'
import { Generator } from 'graphql-static-binding/dist/types.d'
import { schemaFilePath, srcPath } from './paths'
export async function generateSchema() {
const schema = await fs.readFile(schemaFilePath, 'utf8')
const generator: Generator = {
...gcgenerator,
Main: renderMainMethod,
RootType: renderRootType,
Header: renderHeader,
SubscriptionType: renderSubscriptionType,
SchemaType: renderSchemaInterface,
GraphQLEnumType: renderEnumType,
}
const outPath = path.join(srcPath, 'generated', 'schema.ts')
await fs.writeFile(outPath, generateCode(schema, generator), 'utf8')
console.log(`Generated typings for schema to ${outPath}`)
}
function renderHeader(): string {
return `// autogenerated from schema.graphql by running yarn gql:gen
import { GraphQLResolveInfo } from "graphql"\n
import { Context } from "../utils"
`
}
function renderMainMethod() {
return ``
}
function renderEnumType(type) {
const fieldDefinition = type
.getValues()
.map(function(e) {
return ` ${e.name} = '${e.name}'`
})
.join(',\n')
return `${renderDescription(type.description)}export enum ${
type.name
} { \n${fieldDefinition}\n}`
}
function renderRootType(type: GraphQLObjectType): string {
const fieldDefinition = Object.keys(type.getFields())
.map(f => {
const field = type.getFields()[f]
return ` export type ${field.name} = (parent: any, args: {${
field.args.length > 0 ? ' ' : ''
}${field.args
.map(f => `${renderFieldName(f)}: ${renderFieldType(f.type)}`)
.join(', ')}${
field.args.length > 0 ? ' ' : ''
}}, context: Context, info?: GraphQLResolveInfo | string) => Promise<${renderFieldType(
field.type,
)}${!isNonNullType(field.type) ? ' | null' : ''}>`
})
.join('\n')
return `${renderDescription(type.description)}export namespace ${
type.name
} {\n${fieldDefinition}\n}`
}
function renderSubscriptionType(type: GraphQLObjectType): string {
const fieldDefinition = Object.keys(type.getFields())
.map(f => {
const field = type.getFields()[f]
return ` export type ${field.name} = (args: {${
field.args.length > 0 ? ' ' : ''
}${field.args
.map(f => `${renderFieldName(f)}: ${renderFieldType(f.type)}`)
.join(', ')}${
field.args.length > 0 ? ' ' : ''
}}, context: Context, infoOrQuery?: GraphQLResolveInfo | string) => Promise<AsyncIterator<${renderFieldType(
field.type,
)}>>`
})
.join('\n')
return `${renderDescription(type.description)}export namespace ${
type.name
} {\n${fieldDefinition}\n}`
}
function renderSchemaInterface() {
return ''
}
function renderDescription(description) {
return (
'' +
(description
? '/*\n' +
description.split('\n').map(function(l) {
return ' * ' + l + '\n'
}) +
'\n */\n'
: '')
)
} |
Could you create a PR with a this new generator? |
Well, that's what I was asking at the top of my first comment. I would like to know an opinion from maintainers before going extra lengths of making unwanted PR. Besides, current |
@schickling and @kbrandwijk what do you think? |
Thanks a lot for bringing this up @FredyC @jvbianchi. This is directly related to #65 and should be a lot easier to tackle once this is resolved. In the meanwhile a PR & adding it directly here seems like the best solution. So just to reiterate: What you're planning to do is generating (typed) resolvers (not bindings) based on a provided GraphQL schema? |
@schickling Exactly, it's not really a binding, that's why I wasn't sure if it fits within this repo or if it should go separately. The code above based on the following schema generates typings as below which makes it much easier to write code for resolvers and get type errors in case the schema changes. type AuthPlayer {
playerId: ID!
token: String!
}
input AuthPlayerInput {
name: String!
}
type Mutation {
authPlayer(input: AuthPlayerInput!): AuthPlayer!
}
type Query {
dummy: Boolean
}
type Subscription {
dummy: Boolean
} import { GraphQLResolveInfo } from "graphql"
import { Context } from "../utils"
export interface AuthPlayerInput {
name: String
}
export interface AuthPlayer {
playerId: ID_Output
token: String
}
export type ID_Input = string | number
export type ID_Output = string
export type String = string
export type Boolean = boolean
export namespace Query {
export type dummy = (parent: any, args: {}, context: Context, info?: GraphQLResolveInfo | string) => Promise<Boolean | null>
}
export namespace Mutation {
export type authPlayer = (parent: any, args: { input: AuthPlayerInput }, context: Context, info?: GraphQLResolveInfo | string) => Promise<AuthPlayer>
}
export namespace Subscription {
export type dummy = (parent: any, args: {}, context: Context, infoOrQuery?: GraphQLResolveInfo | string) => Promise<AsyncIterator<Boolean>>
} Besides I don't think that current |
You're right, it's not really a GraphQL binding and we should restructure the project hierarchy accordingly. Until this has happened, I'd kindly ask you to use a fork in the meanwhile. This should be possible by using the I'd like to pick this up again in 1-2 weeks as we've been working on something similar. |
There is a certainly a handful of utility functions within this package that made this generator of mine possible without too much hassle. That's something to consider as well to have these functions separated so it can be reused out of a binding generation concerns. @jvbianchi feel free to do a fork with that code and make it more universal. I don't have a capacity for it right now. |
I am not entirely sure what is the use case for
binding-ts
generator. I assume it's not meant to add type safety to resolvers? Either way, if I would like to make another generator for this case, would you accept PR for it or should it go as a standalone module? Unfortunately, it would be a lot of copy & paste.Let me explain what issues there are for that generator to be used for resolvers properly. Besides regular types, enums, inputs ... which are correctly generated, there is this type.
I cannot figure out how I would use that for typing my resolver. Also, there is an issue of a missing first argument being parent object.
Instead, I've figured out that this approach might serve much better.
Looking at the code it shouldn't be that hard to do required tweaks, but it will be a lot of copy&paste which I am not for fond of :) Opinions?
The text was updated successfully, but these errors were encountered: