import { GraphQLSchema , graphql , isObjectType , GraphQLObjectType , getNamedType , GraphQLNamedType , isNonNullType , GraphQLOutputType , } from 'graphql' ; import { Ignore , ExecuteFn , OnRoute , Method } from './types' ; import { convertName } from './common' ; import { logger } from './logger' ; import { ErrorHandler } from './express' ; interface RouteConfig { method ? : Method ; path ? : string ; responseStatus ? : number ; } export interface Route { method : Method ; path : string ; responseStatus : number ; } export interface SofaConfig { basePath : string ; schema : GraphQLSchema ; execute ? : ExecuteFn ; example ignore ? : Ignore ; onRoute ? : OnRoute ; depthLimit ? : number ; errorHandler ? : ErrorHandler ; /**
     * Overwrites the default HTTP route.
     */ routes ? : Record < string , RouteConfig > ; } export interface Sofa { basePath : string ; schema : GraphQLSchema ; models : string [ ] ; ignore : Ignore ; depthLimit : number ; routes ? : Record < string , RouteConfig > ; execute : ExecuteFn ; onRoute ? : OnRoute ; errorHandler ? : ErrorHandler ; } export function createSofa ( config ) { logger . debug ( '[Sofa] Created' ) ; const models = extractsModels ( config . schema ) ; const ignore = config . ignore || [ ] ; const depthLimit = config . depthLimit || 1 ; logger . debug ( `[Sofa] models: ${ models . join ( ', ' ) }` ) ; logger . debug ( `[Sofa] ignore: ${ ignore . join ( ', ' ) }` ) ; return { execute : graphql , models , ignore , depthLimit , ... config , } ; } function extractsModels ( schema ) { const modelMap = { } ; const query = schema . getQueryType ( ) ! ; const fields = query . getFields ( ) ; for ( const fieldName in fields ) { const field = fields [ fieldName ] ; const namedType = getNamedType ( field . type ) ; if ( hasID ( namedType ) ) { if ( ! modelMap [ namedType . name ] ) { modelMap [ namedType . name ] = { } ; } if ( isArrayOf ( field . type , namedType ) ) { const sameName = isNameEqual ( field . name , namedType . name + 's' ) ; const allOptionalArguments = ! field . args . some ( ( arg ) => isNonNullType ( arg . type ) ) ; modelMap [ namedType . name ] . list = sameName && allOptionalArguments ; } else if ( isObjectType ( field . type ) || ( isNonNullType ( field . type ) && isObjectType ( field . type . ofType ) ) ) { const sameName = isNameEqual ( field . name , namedType . name ) ; const hasIdArgument = field . args . length === 1 && field . args [ 0 ] . name === 'id' ; modelMap [ namedType . name ] . single = sameName && hasIdArgument ; } } } return Object . keys ( modelMap ) . filter ( ( name ) => modelMap [ name ] . list && modelMap [ name ] . single ) ; } function isArrayOf ( type , expected ) { const typeNameInSdl = type . toString ( ) ; return ( typeNameInSdl . includes ( '[' ) && typeNameInSdl . includes ( expected . toString ( ) ) ) ; } function hasID ( type ) { return isObjectType ( type ) && ! ! type . getFields ( ) . id ; } function isNameEqual ( a , b ) { return convertName ( a ) === convertName ( b ) ; }