import { DirectiveLocation } from 'graphql' ; import { FeatureDefinition , FeatureDefinitions , FeatureUrl , FeatureVersion } from "./coreSpec" ; import { DirectiveDefinition , EnumType , ScalarType , Schema , NonNullType , } from "./definitions" ; import { Subgraph , Subgraphs } from "./federation" ; import { MultiMap } from "./utils" ; export const joinIdentity = 'https://specs.apollo.dev/join' ; function sanitizeGraphQLName ( name ) { const alphaNumericUnderscoreOnly = name . replace ( /[\W]/g , '_' ) ; const noNumericFirstChar = alphaNumericUnderscoreOnly . match ( /^\d/ ) ? '_' + alphaNumericUnderscoreOnly : alphaNumericUnderscoreOnly ; const noUnderscoreNumericEnding = noNumericFirstChar . match ( /_\d+$/ ) ? noNumericFirstChar + '_' : noNumericFirstChar ; const toUpper = noUnderscoreNumericEnding . toLocaleUpperCase ( ) ; return toUpper ; } export class JoinSpecDefinition extends FeatureDefinition { constructor ( version ) { super ( new FeatureUrl ( joinIdentity , 'join' , version ) ) ; } private isV01 ( ) { return this . version . equals ( new FeatureVersion ( 0 , 1 ) ) ; } addElementsToSchema ( schema ) { const joinGraph = this . addDirective ( schema , 'graph' ) . addLocations ( DirectiveLocation . ENUM_VALUE ) ; joinGraph . addArgument ( 'name' , new NonNullType ( schema . stringType ( ) ) ) ; joinGraph . addArgument ( 'url' , new NonNullType ( schema . stringType ( ) ) ) ; const graphEnum = this . addEnumType ( schema , 'Graph' ) ; const joinFieldSet = this . addScalarType ( schema , 'FieldSet' ) ; const joinType = this . addDirective ( schema , 'type' ) . addLocations ( DirectiveLocation . OBJECT , DirectiveLocation . INTERFACE , DirectiveLocation . UNION , DirectiveLocation . ENUM , DirectiveLocation . INPUT_OBJECT , DirectiveLocation . SCALAR ) ; if ( ! this . isV01 ( ) ) { joinType . repeatable = true ; } joinType . addArgument ( 'graph' , new NonNullType ( graphEnum ) ) ; joinType . addArgument ( 'key' , joinFieldSet ) ; if ( ! this . isV01 ( ) ) { joinType . addArgument ( 'extension' , new NonNullType ( schema . booleanType ( ) ) , false ) ; } const joinField = this . addDirective ( schema , 'field' ) . addLocations ( DirectiveLocation . FIELD_DEFINITION , DirectiveLocation . INPUT_FIELD_DEFINITION ) ; joinField . repeatable = true ; joinField . addArgument ( 'graph' , new NonNullType ( graphEnum ) ) ; joinField . addArgument ( 'requires' , joinFieldSet ) ; joinField . addArgument ( 'provides' , joinFieldSet ) ; if ( ! this . isV01 ( ) ) { joinField . addArgument ( 'type' , schema . stringType ( ) ) ; joinField . addArgument ( 'external' , schema . booleanType ( ) ) ; } if ( ! this . isV01 ( ) ) { const joinImplements = this . addDirective ( schema , 'implements' ) . addLocations ( DirectiveLocation . OBJECT , DirectiveLocation . INTERFACE ) ; joinImplements . repeatable = true ; joinImplements . addArgument ( 'graph' , new NonNullType ( graphEnum ) ) ; joinImplements . addArgument ( 'interface' , new NonNullType ( schema . stringType ( ) ) ) ; } if ( this . isV01 ( ) ) { const joinOwner = this . addDirective ( schema , 'owner' ) . addLocations ( DirectiveLocation . OBJECT ) ; joinOwner . addArgument ( 'graph' , new NonNullType ( graphEnum ) ) ; } } populateGraphEnum ( schema , subgraphs ) { const sanitizedNameToSubgraphs = new MultiMap < string , Subgraph > ( ) ; for ( const subgraph of subgraphs ) { const sanitized = sanitizeGraphQLName ( subgraph . name ) ; sanitizedNameToSubgraphs . add ( sanitized , subgraph ) ; } const subgraphToEnumName = new Map < string , string > ( ) ; for ( const [ sanitizedName , subgraphsForName ] of sanitizedNameToSubgraphs ) { if ( subgraphsForName . length === 1 ) { subgraphToEnumName . set ( subgraphsForName [ 0 ] . name , sanitizedName ) ; } else { for ( const [ index , subgraph ] of subgraphsForName . entries ( ) ) { subgraphToEnumName . set ( subgraph . name , `${ sanitizedName }_${ index + 1 }` ) ; } } } const graphEnum = this . graphEnum ( schema ) ; const graphDirective = this . graphDirective ( schema ) ; for ( const subgraph of subgraphs ) { const enumValue = graphEnum . addValue ( subgraphToEnumName . get ( subgraph . name ) ! ) ; enumValue . applyDirective ( graphDirective , { name : subgraph . name , url : subgraph . url } ) ; } return subgraphToEnumName ; } fieldSetScalar ( schema ) { return this . type ( schema , 'FieldSet' ) ! ; } graphEnum ( schema ) { return this . type ( schema , 'Graph' ) ! ; } graphDirective ( schema ) { return this . directive ( schema , 'graph' ) ! ; } typeDirective ( schema ) { return this . directive ( schema , 'type' ) ! ; } implementsDirective ( schema ) { return this . directive ( schema , 'implements' ) ; } fieldDirective ( schema ) { return this . directive ( schema , 'field' ) ! ; } ownerDirective ( schema ) { return this . directive ( schema , 'owner' ) ; } } export const JOIN_VERSIONS = new FeatureDefinitions < JoinSpecDefinition > ( joinIdentity ) . add ( new JoinSpecDefinition ( new FeatureVersion ( 0 , 1 ) ) ) . add ( new JoinSpecDefinition ( new FeatureVersion ( 0 , 2 ) ) ) ;