@@ -66,6 +66,11 @@ import {
6666 addSubgraphToError ,
6767 printHumanReadableList ,
6868 ArgumentMerger ,
69+ JoinSpecDefinition ,
70+ CoreSpecDefinition ,
71+ FeatureVersion ,
72+ FEDERATION_VERSIONS ,
73+ InaccessibleSpecDefinition ,
6974} from "@apollo/federation-internals" ;
7075import { ASTNode , GraphQLError , DirectiveLocation } from "graphql" ;
7176import {
@@ -79,13 +84,8 @@ import { inspect } from "util";
7984import { collectCoreDirectivesToCompose , CoreDirectiveInSubgraphs } from "./coreDirectiveCollector" ;
8085import { CompositionOptions } from "../compose" ;
8186
82-
83- const linkSpec = LINK_VERSIONS . latest ( ) ;
8487type FieldOrUndefinedArray = ( FieldDefinition < any > | undefined ) [ ] ;
8588
86- const joinSpec = JOIN_VERSIONS . latest ( ) ;
87- const inaccessibleSpec = INACCESSIBLE_VERSIONS . latest ( ) ;
88-
8989export type MergeResult = MergeSuccess | MergeFailure ;
9090
9191type FieldMergeContextProperties = {
@@ -275,8 +275,17 @@ class Merger {
275275 sources : ( SchemaElement < any , any > | undefined ) [ ] ,
276276 dest : SchemaElement < any , any > ,
277277 } [ ] ;
278+ private joinSpec : JoinSpecDefinition ;
279+ private linkSpec : CoreSpecDefinition ;
280+ private inaccessibleSpec : InaccessibleSpecDefinition ;
281+ private latestFedVersionUsed : FeatureVersion ;
278282
279283 constructor ( readonly subgraphs : Subgraphs , readonly options : CompositionOptions ) {
284+ this . latestFedVersionUsed = this . getLatestFederationVersionUsed ( ) ;
285+ this . joinSpec = JOIN_VERSIONS . getMinimumRequiredVersion ( this . latestFedVersionUsed ) ;
286+ this . linkSpec = LINK_VERSIONS . getMinimumRequiredVersion ( this . latestFedVersionUsed ) ;
287+ this . inaccessibleSpec = INACCESSIBLE_VERSIONS . getMinimumRequiredVersion ( this . latestFedVersionUsed ) ;
288+
280289 this . names = subgraphs . names ( ) ;
281290 this . composeDirectiveManager = new ComposeDirectiveManager (
282291 this . subgraphs ,
@@ -293,20 +302,34 @@ class Merger {
293302 this . appliedDirectivesToMerge = [ ] ;
294303 }
295304
305+ private getLatestFederationVersionUsed ( ) : FeatureVersion {
306+ const latestVersion = this . subgraphs . values ( ) . reduce ( ( latest : FeatureVersion | undefined , subgraph ) => {
307+ const version = subgraph . metadata ( ) ?. federationFeature ( ) ?. url ?. version ;
308+ if ( ! latest ) {
309+ return version ;
310+ }
311+ if ( ! version ) {
312+ return latest ;
313+ }
314+ return latest >= version ? latest : version ;
315+ } , undefined ) ;
316+ return latestVersion ?? FEDERATION_VERSIONS . latest ( ) . version ;
317+ }
318+
296319 private prepareSupergraph ( ) : Map < string , string > {
297320 // TODO: we will soon need to look for name conflicts for @core and @join with potentially user-defined directives and
298321 // pass a `as` to the methods below if necessary. However, as we currently don't propagate any subgraph directives to
299322 // the supergraph outside of a few well-known ones, we don't bother yet.
300- linkSpec . addToSchema ( this . merged ) ;
301- const errors = linkSpec . applyFeatureToSchema ( this . merged , joinSpec , undefined , joinSpec . defaultCorePurpose ) ;
323+ this . linkSpec . addToSchema ( this . merged ) ;
324+ const errors = this . linkSpec . applyFeatureToSchema ( this . merged , this . joinSpec , undefined , this . joinSpec . defaultCorePurpose ) ;
302325 assert ( errors . length === 0 , "We shouldn't have errors adding the join spec to the (still empty) supergraph schema" ) ;
303326
304327 const directivesMergeInfo = collectCoreDirectivesToCompose ( this . subgraphs ) ;
305328 for ( const mergeInfo of directivesMergeInfo ) {
306329 this . validateAndMaybeAddSpec ( mergeInfo ) ;
307330 }
308331
309- return joinSpec . populateGraphEnum ( this . merged , this . subgraphs ) ;
332+ return this . joinSpec . populateGraphEnum ( this . merged , this . subgraphs ) ;
310333 }
311334
312335 private validateAndMaybeAddSpec ( { feature, name, definitionsPerSubgraph, compositionSpec} : CoreDirectiveInSubgraphs ) {
@@ -339,8 +362,8 @@ class Merger {
339362 // If we get here with `nameInSupergraph` unset, it means there is no usage for the directive at all and we
340363 // don't bother adding the spec to the supergraph.
341364 if ( nameInSupergraph ) {
342- const specInSupergraph = compositionSpec . supergraphSpecification ( ) ;
343- const errors = linkSpec . applyFeatureToSchema ( this . merged , specInSupergraph , nameInSupergraph === specInSupergraph . url . name ? undefined : nameInSupergraph , specInSupergraph . defaultCorePurpose ) ;
365+ const specInSupergraph = compositionSpec . supergraphSpecification ( this . latestFedVersionUsed ) ;
366+ const errors = this . linkSpec . applyFeatureToSchema ( this . merged , specInSupergraph , nameInSupergraph === specInSupergraph . url . name ? undefined : nameInSupergraph , specInSupergraph . defaultCorePurpose ) ;
344367 assert ( errors . length === 0 , "We shouldn't have errors adding the join spec to the (still empty) supergraph schema" ) ;
345368 const argumentsMerger = compositionSpec . argumentsMerger ?. call ( null , this . merged ) ;
346369 if ( argumentsMerger instanceof GraphQLError ) {
@@ -395,7 +418,7 @@ class Merger {
395418 this . addDirectivesShallow ( ) ;
396419
397420 const typesToMerge = this . merged . types ( )
398- . filter ( ( type ) => ! linkSpec . isSpecType ( type ) && ! joinSpec . isSpecType ( type ) ) ;
421+ . filter ( ( type ) => ! this . linkSpec . isSpecType ( type ) && ! this . joinSpec . isSpecType ( type ) ) ;
399422
400423 // Then, for object and interface types, we merge the 'implements' relationship, and we merge the unions.
401424 // We do this first because being able to know if a type is a subtype of another one (which relies on those
@@ -426,7 +449,7 @@ class Merger {
426449
427450 for ( const definition of this . merged . directives ( ) ) {
428451 // we should skip the supergraph specific directives, that is the @core and @join directives.
429- if ( linkSpec . isSpecDirective ( definition ) || joinSpec . isSpecDirective ( definition ) ) {
452+ if ( this . linkSpec . isSpecDirective ( definition ) || this . joinSpec . isSpecDirective ( definition ) ) {
430453 continue ;
431454 }
432455 this . mergeDirectiveDefinition ( this . subgraphsSchema . map ( s => s . directive ( definition . name ) ) , definition ) ;
@@ -622,7 +645,7 @@ class Merger {
622645
623646 private mergeImplements < T extends ObjectType | InterfaceType > ( sources : ( T | undefined ) [ ] , dest : T ) {
624647 const implemented = new Set < string > ( ) ;
625- const joinImplementsDirective = joinSpec . implementsDirective ( this . merged ) ! ;
648+ const joinImplementsDirective = this . joinSpec . implementsDirective ( this . merged ) ! ;
626649 for ( const [ idx , source ] of sources . entries ( ) ) {
627650 if ( source ) {
628651 const name = this . joinSpecName ( idx ) ;
@@ -753,7 +776,7 @@ class Merger {
753776 }
754777
755778 private addJoinType ( sources : ( NamedType | undefined ) [ ] , dest : NamedType ) {
756- const joinTypeDirective = joinSpec . typeDirective ( this . merged ) ;
779+ const joinTypeDirective = this . joinSpec . typeDirective ( this . merged ) ;
757780 for ( const [ idx , source ] of sources . entries ( ) ) {
758781 if ( ! source ) {
759782 continue ;
@@ -914,7 +937,7 @@ class Merger {
914937 // clarify to the later extraction process that this particular field doesn't come
915938 // from any particular subgraph (it comes indirectly from an @interfaceObject type,
916939 // but it's very much indirect so ...).
917- implemField . applyDirective ( joinSpec . fieldDirective ( this . merged ) , { graph : undefined } ) ;
940+ implemField . applyDirective ( this . joinSpec . fieldDirective ( this . merged ) , { graph : undefined } ) ;
918941
919942
920943 // If we had to add a field here, it means that, for this particular implementation, the
@@ -937,7 +960,7 @@ class Merger {
937960 // implementation type when @interfaceObject is used. But we shouldn't copy the `join` spec directive
938961 // as those are for the interface field but are invalid for the implementation field.
939962 source . appliedDirectives . forEach ( ( d ) => {
940- if ( ! joinSpec . isSpecDirective ( d . definition ! ) ) {
963+ if ( ! this . joinSpec . isSpecDirective ( d . definition ! ) ) {
941964 dest . applyDirective ( d . name , { ...d . arguments ( ) } )
942965 }
943966 } ) ;
@@ -1537,7 +1560,7 @@ class Merger {
15371560 } ) ) {
15381561 return ;
15391562 }
1540- const joinFieldDirective = joinSpec . fieldDirective ( this . merged ) ;
1563+ const joinFieldDirective = this . joinSpec . fieldDirective ( this . merged ) ;
15411564 for ( const [ idx , source ] of sources . entries ( ) ) {
15421565 const usedOverridden = mergeContext . isUsedOverridden ( idx ) ;
15431566 const unusedOverridden = mergeContext . isUnusedOverridden ( idx ) ;
@@ -1892,7 +1915,7 @@ class Merger {
18921915 }
18931916
18941917 private addJoinUnionMember ( sources : ( UnionType | undefined ) [ ] , dest : UnionType , member : ObjectType ) {
1895- const joinUnionMemberDirective = joinSpec . unionMemberDirective ( this . merged ) ;
1918+ const joinUnionMemberDirective = this . joinSpec . unionMemberDirective ( this . merged ) ;
18961919 // We should always be merging with the latest join spec, so this should exists, but well, in prior versions where
18971920 // the directive didn't existed, we simply did had any replacement so ...
18981921 if ( ! joinUnionMemberDirective ) {
@@ -1989,7 +2012,7 @@ class Merger {
19892012 this . recordAppliedDirectivesToMerge ( valueSources , value ) ;
19902013 this . addJoinEnumValue ( valueSources , value ) ;
19912014
1992- const inaccessibleInSupergraph = this . mergedFederationDirectiveInSupergraph . get ( inaccessibleSpec . inaccessibleDirectiveSpec . name ) ;
2015+ const inaccessibleInSupergraph = this . mergedFederationDirectiveInSupergraph . get ( this . inaccessibleSpec . inaccessibleDirectiveSpec . name ) ;
19932016 const isInaccessible = inaccessibleInSupergraph && value . hasAppliedDirective ( inaccessibleInSupergraph . definition ) ;
19942017 // The merging strategy depends on the enum type usage:
19952018 // - if it is _only_ used in position of Input type, we merge it with an "intersection" strategy (like other input types/things).
@@ -2038,7 +2061,7 @@ class Merger {
20382061 }
20392062
20402063 private addJoinEnumValue ( sources : ( EnumValue | undefined ) [ ] , dest : EnumValue ) {
2041- const joinEnumValueDirective = joinSpec . enumValueDirective ( this . merged ) ;
2064+ const joinEnumValueDirective = this . joinSpec . enumValueDirective ( this . merged ) ;
20422065 // We should always be merging with the latest join spec, so this should exists, but well, in prior versions where
20432066 // the directive didn't existed, we simply did had any replacement so ...
20442067 if ( ! joinEnumValueDirective ) {
@@ -2080,7 +2103,7 @@ class Merger {
20802103 }
20812104
20822105 private mergeInput ( sources : ( InputObjectType | undefined ) [ ] , dest : InputObjectType ) {
2083- const inaccessibleInSupergraph = this . mergedFederationDirectiveInSupergraph . get ( inaccessibleSpec . inaccessibleDirectiveSpec . name ) ;
2106+ const inaccessibleInSupergraph = this . mergedFederationDirectiveInSupergraph . get ( this . inaccessibleSpec . inaccessibleDirectiveSpec . name ) ;
20842107
20852108 // Like for other inputs, we add all the fields found in any subgraphs initially as a simple mean to have a complete list of
20862109 // field to iterate over, but we will remove those that are not in all subgraphs.
@@ -2355,7 +2378,7 @@ class Merger {
23552378 // is @inaccessible , which is necessary to exist in the supergraph for EnumValues to properly
23562379 // determine whether the fact that a value is both input / output will matter
23572380 private recordAppliedDirectivesToMerge ( sources : ( SchemaElement < any , any > | undefined ) [ ] , dest : SchemaElement < any , any > ) {
2358- const inaccessibleInSupergraph = this . mergedFederationDirectiveInSupergraph . get ( inaccessibleSpec . inaccessibleDirectiveSpec . name ) ;
2381+ const inaccessibleInSupergraph = this . mergedFederationDirectiveInSupergraph . get ( this . inaccessibleSpec . inaccessibleDirectiveSpec . name ) ;
23592382 const inaccessibleName = inaccessibleInSupergraph ?. definition . name ;
23602383 const names = this . gatherAppliedDirectiveNames ( sources ) ;
23612384
0 commit comments