import { inspect } from 'util' ; import { GraphQLObjectType } from 'graphql' ; import { SelectionSet , Selection , Field , FragmentSpread } from '../' ; import { collectAndMergeFields } from './collectAndMergeFields' ; export class Variant implements SelectionSet { constructor ( public possibleTypes , public selections = [ ] , public fragmentSpreads = [ ] ) { } get fields ( ) : Field [ ] { return collectAndMergeFields ( this ) ; } inspect ( ) { return `${ inspect ( this . possibleTypes ) } -> ${ inspect ( collectAndMergeFields ( this , false ) . map ( field => field . responseKey ) ) } ${ inspect ( this . fragmentSpreads . map ( fragmentSpread => fragmentSpread . fragmentName ) ) }\n` ; } } export function typeCaseForSelectionSet ( selectionSet , mergeInFragmentSpreads = true ) { const typeCase = new TypeCase ( selectionSet . possibleTypes ) ; for ( const selection of selectionSet . selections ) { switch ( selection . kind ) { case 'Field' : for ( const variant of typeCase . disjointVariantsFor ( selectionSet . possibleTypes ) ) { variant . selections . push ( selection ) ; } break ; case 'FragmentSpread' : if ( typeCase . default . fragmentSpreads . some ( fragmentSpread => fragmentSpread . fragmentName === selection . fragmentName ) ) continue ; for ( const variant of typeCase . disjointVariantsFor ( selectionSet . possibleTypes ) ) { variant . fragmentSpreads . push ( selection ) ; if ( ! mergeInFragmentSpreads ) { variant . selections . push ( selection ) ; } } if ( mergeInFragmentSpreads ) { typeCase . merge ( typeCaseForSelectionSet ( { possibleTypes : selectionSet . possibleTypes . filter ( type => selection . selectionSet . possibleTypes . includes ( type ) ) , selections : selection . selectionSet . selections , } , mergeInFragmentSpreads ) ) ; } break ; case 'TypeCondition' : typeCase . merge ( typeCaseForSelectionSet ( { possibleTypes : selectionSet . possibleTypes . filter ( type => selection . selectionSet . possibleTypes . includes ( type ) ) , selections : selection . selectionSet . selections , } , mergeInFragmentSpreads ) ) ; break ; case 'BooleanCondition' : typeCase . merge ( typeCaseForSelectionSet ( selection . selectionSet , mergeInFragmentSpreads ) , selectionSet => [ { ... selection , selectionSet , } , ] ) ; break ; } } return typeCase ; } export class TypeCase { default : Variant ; private variantsByType : Map < GraphQLObjectType , Variant > ; get variants ( ) : Variant [ ] { return Array . from ( new Set ( this . variantsByType . values ( ) ) ) ; } get defaultAndVariants ( ) : Variant [ ] { return [ this . default , ... this . variants ] ; } get remainder ( ) : Variant | undefined { if ( this . default . possibleTypes . some ( type => ! this . variantsByType . has ( type ) ) ) { return new Variant ( this . default . possibleTypes . filter ( type => ! this . variantsByType . has ( type ) ) , this . default . selections , this . default . fragmentSpreads ) ; } else { return undefined ; } } get exhaustiveVariants ( ) : Variant [ ] { const remainder = this . remainder ; if ( remainder ) { return [ remainder , ... this . variants ] ; } else { return this . variants ; } } constructor ( possibleTypes ) { this . default = new Variant ( possibleTypes ) ; this . variantsByType = new Map ( ) ; } disjointVariantsFor ( possibleTypes ) { const variants = [ ] ; const matchesDefault = this . default . possibleTypes . every ( type => possibleTypes . includes ( type ) ) ; if ( matchesDefault ) { variants . push ( this . default ) ; } const splits = new Map ( ) ; for ( const type of possibleTypes ) { let original = this . variantsByType . get ( type ) ; if ( ! original ) { if ( matchesDefault ) continue ; original = this . default ; } let split = splits . get ( original ) ; if ( ! split ) { split = new Variant ( [ ] , [ ... original . selections ] , [ ... original . fragmentSpreads ] ) ; splits . set ( original , split ) ; variants . push ( split ) ; } if ( original !== this . default ) { original . possibleTypes . splice ( original . possibleTypes . indexOf ( type ) , 1 ) ; } this . variantsByType . set ( type , split ) ; split . possibleTypes . push ( type ) ; } return variants ; } merge ( otherTypeCase , transform ? ) { for ( const otherVariant of otherTypeCase . defaultAndVariants ) { if ( otherVariant . selections . length < 1 ) continue ; for ( const variant of this . disjointVariantsFor ( otherVariant . possibleTypes ) ) { if ( otherVariant . fragmentSpreads . length > 0 ) { variant . fragmentSpreads = [ ... variant . fragmentSpreads , ... otherVariant . fragmentSpreads ] . filter ( ( a , index , array ) => array . findIndex ( b => b . fragmentName == a . fragmentName ) == index ) ; } variant . selections . push ( ... ( transform ? transform ( otherVariant ) : otherVariant . selections ) ) ; } } } inspect ( ) { return `TypeCase\n` + `  default -> ${ inspect ( this . default ) }\n` + this . variants . map ( variant => `  ${ inspect ( variant ) }\n` ) . join ( '' ) ; } }