import * as fs from 'fs' ; import * as graphql from 'graphql' ; import * as path from 'path' ; import { isAbsolute } from 'path' ; import { DocEntry , processFile } from './processFile' ; function getTypeForString ( typename ) { switch ( typename ) { case 'number' : throw new Error ( 'Ambiguous type "number": please use either float or int' ) ; case 'int' : return graphql . GraphQLInt ; case 'float' : return graphql . GraphQLFloat ; case 'string' : return graphql . GraphQLString ; case 'boolean' : return graphql . GraphQLBoolean ; case 'id' : return graphql . GraphQLID ; default : throw new Error ( `Unknown type ${ typename }` ) ; } } function getFieldConfigMapForModule ( { declarations , module , fieldName , knownTypes = { } , } ) { const fieldConfigMap = { } ; ( declarations [ fieldName ] || [ ] ) . forEach ( ( declaration ) => { const args = { } ; let type ; let isArrayType = false ; if ( declaration . calls ) { const call = declaration . calls [ 0 ] ; if ( call . retTypeIsArray ) { isArrayType = true ; } if ( ! call . retTypeObjProps ) { type = getTypeForString ( call . returnType ?? call . checkerReturnType ! ) ; } else { const objectName = call . returnType ! ; if ( ! ( objectName in knownTypes ) ) { const fields = { } ; knownTypes [ objectName ] = new graphql . GraphQLObjectType ( { name : objectName , fields , } ) ; call . retTypeObjProps . forEach ( ( { name , type } ) => { fields [ name ] = { type : getTypeForString ( type ) , } ; } ) ; if ( declarations [ objectName ] ) { Object . assign ( fields , getFieldConfigMapForModule ( { declarations , module , fieldName : objectName , knownTypes , } ) ) ; } } type = knownTypes [ objectName ] ; } if ( call . parameters ?. [ 1 ] ) { const argParam = call . parameters ! [ 1 ] . param ; if ( ! argParam ) { throw new Error ( `Expecting param in second argument of resolver` ) ; } Object . entries ( argParam . objectProps ) . forEach ( ( [ name , value ] ) => { args [ name ] = { type : getTypeForString ( value ) } ; } ) ; } } const name = declaration . name ! ; const fieldConfig = { type : isArrayType ? new graphql . GraphQLList ( type ! ) : type ! , args , description : declaration . documentation || declaration . calls ?. [ 0 ] . documentation || undefined , resolve ( source , args , context , info ) { return ( module [ fieldName ] [ name ] as Function ) . call ( undefined , source , args , context , info ) ; } , } ; fieldConfigMap [ name ] = fieldConfig ; } ) ; return fieldConfigMap ; } /** Converts code into GraphQLSchema */ export function buildSchemaFromCode ( { modulePath , } ) { if ( ! isAbsolute ( modulePath ) ) { throw new Error ( `Expecting absolute path in ${ modulePath }` ) ; } const declarations = getDeclarationsForModule ( modulePath ) as any ; const module = require ( modulePath ) ; console . log ( 'declarations' , JSON . stringify ( declarations , null , 2 ) ) ; const queryFieldsConfig = getFieldConfigMapForModule ( { declarations , module , fieldName : 'Query' } ) ; const mutationFieldsConfig = getFieldConfigMapForModule ( { declarations , module , fieldName : 'Mutation' , } ) ; const schema = new graphql . GraphQLSchema ( { query : new graphql . GraphQLObjectType ( { name : 'Query' , fields : queryFieldsConfig , } ) , mutation : ( Object . keys ( mutationFieldsConfig ) . length > 0 && new graphql . GraphQLObjectType ( { name : 'Mutation' , fields : mutationFieldsConfig , } ) ) || undefined , } ) ; return schema ; } function getDeclarationsForModule ( absolutePath ) { if ( ! path . isAbsolute ( absolutePath ) ) { throw new Error ( `Expecting an absolute path but received ${ absolutePath }` ) ; } const tsModulePath = path . format ( { ... path . parse ( absolutePath ) , base : undefined , ext : '.ts' , } ) ; if ( fs . existsSync ( tsModulePath ) ) { console . log ( 'reading from' , tsModulePath ) ; return processFile ( tsModulePath ) as any ; } const tsModuleIndexPath = path . join ( absolutePath , './index.ts' ) ; if ( fs . existsSync ( tsModuleIndexPath ) ) { console . log ( 'reading from' , tsModuleIndexPath ) ; return processFile ( tsModuleIndexPath ) as any ; } const graphqlModulePath = path . format ( { ... path . parse ( absolutePath ) , base : undefined , ext : '.graphql.json' , } ) ; if ( fs . existsSync ( graphqlModulePath ) ) { console . log ( 'reading from' , graphqlModulePath ) ; return require ( graphqlModulePath ) ; } throw new Error ( `Neither a TypeScript source file nor GraphQL JSON was found for ${ absolutePath }` ) ; }