Examples & appilcations
vadistic opened this issue ยท 2 comments
For each main are of the project there should be a simple example
- code first graphql SDL
- codegens
- testing graphql stuff
Something like @GavinRay97 example is a good start ๐
https://repl.it/@GavinRay97/AquamarineGoldFibonacci
I think there is feature partity with https://github.com/khaosdoctor/gotql - but better because it's producting AST instead of strings. Simple graphql-extra based client could make nice example
I can give a generic GQL Schema -> Typed Language converter in ~100 lines:
Types:
import {
FieldDefinitionApi,
EnumValueApi,
InputValueApi,
} from 'graphql-extra'
/**
* An Enum for GraphQL scalars
* Used to compose ScalarMaps for language-specific types codegen
*/
export enum ScalarTypes {
ID = 'ID',
INT = 'Int',
FLOAT = 'Float',
STRING = 'String',
BOOLEAN = 'Boolean',
}
/**
* An interface that TypeConverters must implement for how to
* map GraphQL scalars to their corresponding language types
*/
export type ScalarMap = {
[key in ScalarTypes]: string
}
export type Fieldlike = FieldDefinitionApi | InputValueApi
export interface ITypeMap {
types: {
[key: string]: Fieldlike[]
}
enums: {
[key: string]: EnumValueApi[]
}
}
Schema Tools:
import { ITypeMap } from './types'
import {
documentApi,
objectTypeApi,
FieldDefinitionApi,
DocumentApi,
inputTypeApi,
enumValueApi,
enumTypeApi,
} from 'graphql-extra'
/**
* Takes a Document API object and builds a map of it's types and their fields
*/
export function buildTypeMap(document: DocumentApi): ITypeMap {
let res: ITypeMap = {
types: {},
enums: {},
}
for (let [typeName, astNode] of document.typeMap) {
switch (astNode.kind) {
case 'InputObjectTypeDefinition':
res['types'][typeName] = inputTypeApi(astNode).getFields()
break
case 'ObjectTypeDefinition':
res['types'][typeName] = objectTypeApi(astNode).getFields()
break
case 'EnumTypeDefinition':
res['enums'][typeName] = enumTypeApi(astNode).node.values.map(
enumValueApi
)
break
}
}
return res
}
/**
* Checks if type string exists in ScalarMap
*/
export const isScalar = (type: string) => {
return type.toUpperCase() in ScalarTypes
}
/**
* Takes a Field from graphql-extra's FieldDefinitionApi or InputValueApi
* and serializes it to extract and format the important information:
* Name, Type, Nullability, and whether it's a list
*/
export const serialize = (field: Fieldlike) => ({
name: field.getName(),
required: field.isNonNullType(),
list: field.isListType(),
type: field.getTypename(),
})
GQL -> Typescript Converter:
import { ScalarTypes, Fieldlike, ITypeMap } from './types'
import { buildTypeMap } from './schemaTools'
import { html as template } from 'common-tags'
import { EnumValueApi } from 'graphql-extra'
const scalarMap = {
[ScalarTypes.ID]: 'number',
[ScalarTypes.INT]: 'number',
[ScalarTypes.FLOAT]: 'number',
[ScalarTypes.STRING]: 'string',
[ScalarTypes.BOOLEAN]: 'boolean',
}
const baseTypes = template`
type Maybe<T> = T | null
`
const fieldFormatter = (field: Fieldlike) => {
let { name, required, list, type } = serialize(field)
let T = isScalar(type) ? scalarMap[type] : type
// string -> Maybe<string>
if (!required) T = `Maybe<${T}>`
// Maybe<string> -> Array<Maybe<string>>
if (list) T = `Array<${T}>`
// Array<Maybe<string>> -> Maybe<Array<Maybe<string>>>
if (!required && list) T = `Maybe<${T}>`
// username: string -> username?: string
if (!required) name = `${name}?`
return { name, type: T }
}
const tsTypeDef = (typeName: string, fields: Fieldlike[]): string => {
const fieldDefs = fields
.map(fieldFormatter)
.map(({ name, type }) => `${name}: ${type}`)
.join('\n')
return template`
type ${typeName} = {
${fieldDefs}
}`
}
const typeMapToTSTypes = (typeMap: ITypeMap) =>
Object.entries(typeMap.types)
.map(([typeName, fields]) => tsTypeDef(typeName, fields))
.join('\n\n')
const tsEnumDef = (typeName: string, fields: EnumValueApi[]): string => {
const fieldDefs = fields
.map((field) => `${field.getName()} = '${field.getName()}'`)
.join(',\n')
return template`
enum ${typeName} {
${fieldDefs}
}`
}
const typeMapToTSEnums = (typeMap: ITypeMap) =>
Object.entries(typeMap.enums)
.map(([typeName, fields]) => tsEnumDef(typeName, fields))
.join('\n\n')
const typeMapToTypescript = (typeMap: ITypeMap) =>
baseTypes +
'\n\n' +
typeMapToTSTypes(typeMap) +
'\n\n' +
typeMapToTSEnums(typeMap)
export const graphqlSchemaToTypescript = (schema: string) =>
typeMapToTypescript(buildTypeMap(schema))
This same pattern holds for any language
GQL -> Go Converter:
import { ScalarTypes, Fieldlike, ITypeMap } from './types'
import { buildTypeMap, serialize, isScalar } from './schemaTools'
import { html as template } from 'common-tags'
import { EnumValueApi } from 'graphql-extra'
const scalarMap = {
[ScalarTypes.ID]: `int`,
[ScalarTypes.INT]: `int`,
[ScalarTypes.FLOAT]: `float32`,
[ScalarTypes.STRING]: `string`,
[ScalarTypes.BOOLEAN]: `bool`,
}
const fieldFormatter = (field: Fieldlike) => {
let { name, required, list, type } = serialize(field)
let T = isScalar(type) ? scalarMap[type] : type
if (!required) T = `*${T}`
if (list) T = `[]${T}`
return { name, type: T }
}
const goTypeDef = (typeName, fields: Fieldlike[]) => {
const fieldDefs = fields
.map(fieldFormatter)
.map(({ name, type }) => `${name} ${type}`)
.join('\n')
return template`
type ${typeName} struct {
${fieldDefs}
}
`
}
const typeMapToGoTypes = (typeMap: ITypeMap) =>
Object.entries(typeMap.types)
.map(([typeName, fields]) => goTypeDef(typeName, fields))
.join('\n\n')
// type LeaveType string
// const(
// AnnualLeave LeaveType = "AnnualLeave"
// Sick = "Sick"
// BankHoliday = "BankHoliday"
// Other = "Other"
// )
const goEnumDef = (typeName, fields: EnumValueApi[]) => {
const fieldDefs = fields
.map((field, idx) =>
idx == 0
? `${field.getName()} ${typeName} = "${field.getName()}"`
: `${field.getName()} = "${field.getName()}"`
)
.join('\n')
return template`
type ${typeName} string
const(
${fieldDefs}
)
`
}
const typeMapToGoEnums = (typeMap: ITypeMap) =>
Object.entries(typeMap.enums)
.map(([typeName, fields]) => goEnumDef(typeName, fields))
.join('\n\n')
const typeMapToGo = (typeMap: ITypeMap) =>
typeMapToGoTypes(typeMap) + '\n\n' + typeMapToGoEnums(typeMap)
export const graphqlSchemaToGo = (schema: string) =>
typeMapToGo(buildTypeMap(schema))
Demo:
import { graphqlSchemaToGo } from './go'
import { graphqlSchemaToTypescript } from './typescript'
const schema = `
type Mutation {
InsertUserAction(user_info: UserInfo!): TokenOutput
}
enum SOME_ENUM {
TYPE_A
TYPE_B
TYPE_C
}
input UserInfo {
username: String!
password: String!
enum_field: SOME_ENUM!
nullable_field: Float
nullable_list: [Int]
}
type TokenOutput {
accessToken: String!
}
`
console.log(graphqlSchemaToTypescript(schema))
console.log(graphqlSchemaToGo(schema))
I have Java, Kotlin, JSDoc, and Python as well ๐
(There may be better ways to use graphql-extra
than I am here, would love input on that, I got most of this from trying to read the source code)
It would be cool to generate typedefs for query components (IE React/Vue + Apollo) now that the Query/Mutation and SelectionSet stuff is done. I think it may be possible to emulate most of what graphql-code-generator
does in a compact amount of code that's very straightforward.