@@ -560,6 +560,8 @@ export class BaseResolversVisitor<
560
560
protected _usedMappers : { [ key : string ] : boolean } = { } ;
561
561
protected _resolversTypes : ResolverTypes = { } ;
562
562
protected _resolversParentTypes : ResolverParentTypes = { } ;
563
+ protected _hasReferencedResolversUnionTypes = false ;
564
+ protected _resolversUnionTypes : Record < string , string > = { } ;
563
565
protected _rootTypeNames = new Set < string > ( ) ;
564
566
protected _globalDeclarations = new Set < string > ( ) ;
565
567
protected _federation : ApolloFederation ;
@@ -624,6 +626,7 @@ export class BaseResolversVisitor<
624
626
name => this . getParentTypeToUse ( name ) ,
625
627
namedType => ! isEnumType ( namedType )
626
628
) ;
629
+ this . _resolversUnionTypes = this . createResolversUnionTypes ( ) ;
627
630
this . _fieldContextTypeMap = this . createFieldContextTypeMap ( ) ;
628
631
this . _directiveContextTypesMap = this . createDirectivedContextType ( ) ;
629
632
this . _directiveResolverMappings = rawConfig . directiveResolverMappings ?? { } ;
@@ -754,10 +757,9 @@ export class BaseResolversVisitor<
754
757
} else if ( isScalar ) {
755
758
prev [ typeName ] = applyWrapper ( this . _getScalar ( typeName ) ) ;
756
759
} else if ( isUnionType ( schemaType ) ) {
757
- prev [ typeName ] = schemaType
758
- . getTypes ( )
759
- . map ( type => getTypeToUse ( type . name ) )
760
- . join ( ' | ' ) ;
760
+ this . _hasReferencedResolversUnionTypes = true ;
761
+ const resolversType = this . convertName ( 'ResolversUnionTypes' ) ;
762
+ prev [ typeName ] = applyWrapper ( `${ resolversType } ['${ typeName } ']` ) ;
761
763
} else if ( isEnumType ( schemaType ) ) {
762
764
prev [ typeName ] = this . convertName ( typeName , { useTypesPrefix : this . config . enumPrefix } , true ) ;
763
765
} else {
@@ -766,44 +768,11 @@ export class BaseResolversVisitor<
766
768
}
767
769
768
770
if ( shouldApplyOmit && prev [ typeName ] !== 'any' && isObjectType ( schemaType ) ) {
769
- const fields = schemaType . getFields ( ) ;
770
- const relevantFields : {
771
- addOptionalSign : boolean ;
772
- fieldName : string ;
773
- replaceWithType : string ;
774
- } [ ] = this . _federation
775
- . filterFieldNames ( Object . keys ( fields ) )
776
- . filter ( fieldName => {
777
- const field = fields [ fieldName ] ;
778
- const baseType = getBaseType ( field . type ) ;
779
-
780
- // Filter out fields of types that are not included
781
- if ( shouldInclude && ! shouldInclude ( baseType ) ) {
782
- return false ;
783
- }
784
- return true ;
785
- } )
786
- . map ( fieldName => {
787
- const field = fields [ fieldName ] ;
788
- const baseType = getBaseType ( field . type ) ;
789
- const isUnion = isUnionType ( baseType ) ;
790
-
791
- if ( ! this . config . mappers [ baseType . name ] && ! isUnion && ! this . _shouldMapType [ baseType . name ] ) {
792
- return null ;
793
- }
794
-
795
- const addOptionalSign = ! this . config . avoidOptionals && ! isNonNullType ( field . type ) ;
796
-
797
- return {
798
- addOptionalSign,
799
- fieldName,
800
- replaceWithType : wrapTypeWithModifiers ( getTypeToUse ( baseType . name ) , field . type , {
801
- wrapOptional : this . applyMaybe ,
802
- wrapArray : this . wrapWithArray ,
803
- } ) ,
804
- } ;
805
- } )
806
- . filter ( a => a ) ;
771
+ const relevantFields = this . getRelevantFieldsToOmit ( {
772
+ schemaType,
773
+ getTypeToUse,
774
+ shouldInclude,
775
+ } ) ;
807
776
808
777
if ( relevantFields . length > 0 ) {
809
778
// Puts ResolverTypeWrapper on top of an entire type
@@ -819,15 +788,15 @@ export class BaseResolversVisitor<
819
788
}
820
789
821
790
if ( ! isMapped && hasDefaultMapper && hasPlaceholder ( this . config . defaultMapper . type ) ) {
822
- // Make sure the inner type has no ResolverTypeWrapper
823
- const name = clearWrapper ( isScalar ? this . _getScalar ( typeName ) : prev [ typeName ] ) ;
824
- const replaced = replacePlaceholder ( this . config . defaultMapper . type , name ) ;
791
+ const originalTypeName = isScalar ? this . _getScalar ( typeName ) : prev [ typeName ] ;
825
792
826
- // Don't wrap Union with ResolverTypeWrapper, each inner type already has it
827
793
if ( isUnionType ( schemaType ) ) {
828
- prev [ typeName ] = replaced ;
794
+ // Don't clear ResolverTypeWrapper from Unions
795
+ prev [ typeName ] = replacePlaceholder ( this . config . defaultMapper . type , originalTypeName ) ;
829
796
} else {
830
- prev [ typeName ] = applyWrapper ( replacePlaceholder ( this . config . defaultMapper . type , name ) ) ;
797
+ const name = clearWrapper ( originalTypeName ) ;
798
+ const replaced = replacePlaceholder ( this . config . defaultMapper . type , name ) ;
799
+ prev [ typeName ] = applyWrapper ( replaced ) ;
831
800
}
832
801
}
833
802
@@ -837,7 +806,7 @@ export class BaseResolversVisitor<
837
806
838
807
protected replaceFieldsInType (
839
808
typeName : string ,
840
- relevantFields : { addOptionalSign : boolean ; fieldName : string ; replaceWithType : string } [ ]
809
+ relevantFields : ReturnType < typeof this . getRelevantFieldsToOmit >
841
810
) : string {
842
811
this . _globalDeclarations . add ( OMIT_TYPE ) ;
843
812
return `Omit<${ typeName } , ${ relevantFields . map ( f => `'${ f . fieldName } '` ) . join ( ' | ' ) } > & { ${ relevantFields
@@ -880,6 +849,64 @@ export class BaseResolversVisitor<
880
849
return `Array<${ t } >` ;
881
850
}
882
851
852
+ protected createResolversUnionTypes ( ) : Record < string , string > {
853
+ if ( ! this . _hasReferencedResolversUnionTypes ) {
854
+ return { } ;
855
+ }
856
+
857
+ const allSchemaTypes = this . _schema . getTypeMap ( ) ;
858
+ const typeNames = this . _federation . filterTypeNames ( Object . keys ( allSchemaTypes ) ) ;
859
+
860
+ const unionTypes = typeNames . reduce ( ( res , typeName ) => {
861
+ const schemaType = allSchemaTypes [ typeName ] ;
862
+
863
+ if ( isUnionType ( schemaType ) ) {
864
+ const referencedTypes = schemaType . getTypes ( ) . map ( unionMemberType => {
865
+ const isUnionMemberMapped = this . config . mappers [ unionMemberType . name ] ;
866
+
867
+ // 1. If mapped without placehoder, just use it without doing extra checks
868
+ if ( isUnionMemberMapped && ! hasPlaceholder ( isUnionMemberMapped . type ) ) {
869
+ return isUnionMemberMapped . type ;
870
+ }
871
+
872
+ // 2. Work out value for union member type
873
+ // 2a. By default, use the typescript type
874
+ let unionMemberValue = this . convertName ( unionMemberType . name , { } , true ) ;
875
+
876
+ // 2b. Find fields to Omit if needed.
877
+ // - If no field to Omit, "type with maybe Omit" is typescript type i.e. no Omit
878
+ // - If there are fields to Omit, "type with maybe Omit"
879
+ const fieldsToOmit = this . getRelevantFieldsToOmit ( {
880
+ schemaType : unionMemberType ,
881
+ getTypeToUse : this . getTypeToUse ,
882
+ } ) ;
883
+ if ( fieldsToOmit . length > 0 ) {
884
+ unionMemberValue = this . replaceFieldsInType ( unionMemberValue , fieldsToOmit ) ;
885
+ }
886
+
887
+ // 2c. If union member is mapped with placeholder, use the "type with maybe Omit" as {T}
888
+ if ( isUnionMemberMapped && hasPlaceholder ( isUnionMemberMapped . type ) ) {
889
+ return replacePlaceholder ( isUnionMemberMapped . type , unionMemberValue ) ;
890
+ }
891
+
892
+ // 2d. If has default mapper with placeholder, use the "type with maybe Omit" as {T}
893
+ const hasDefaultMapper = ! ! this . config . defaultMapper ?. type ;
894
+ const isScalar = this . config . scalars [ typeName ] ;
895
+ if ( hasDefaultMapper && hasPlaceholder ( this . config . defaultMapper . type ) ) {
896
+ const finalTypename = isScalar ? this . _getScalar ( typeName ) : unionMemberValue ;
897
+ return replacePlaceholder ( this . config . defaultMapper . type , finalTypename ) ;
898
+ }
899
+
900
+ return unionMemberValue ;
901
+ } ) ;
902
+ res [ typeName ] = referencedTypes . map ( type => `( ${ type } )` ) . join ( ' | ' ) ; // Must wrap every union member in explicit "( )" to separate the members
903
+ }
904
+ return res ;
905
+ } , { } ) ;
906
+
907
+ return unionTypes ;
908
+ }
909
+
883
910
protected createFieldContextTypeMap ( ) : FieldContextTypeMap {
884
911
return this . config . fieldContextTypes . reduce < FieldContextTypeMap > ( ( prev , fieldContextType ) => {
885
912
const items = fieldContextType . split ( '#' ) ;
@@ -935,6 +962,24 @@ export class BaseResolversVisitor<
935
962
) . string ;
936
963
}
937
964
965
+ public buildResolversUnionTypes ( ) : string {
966
+ if ( Object . keys ( this . _resolversUnionTypes ) . length === 0 ) {
967
+ return '' ;
968
+ }
969
+
970
+ const declarationKind = 'type' ;
971
+ return new DeclarationBlock ( this . _declarationBlockConfig )
972
+ . export ( )
973
+ . asKind ( declarationKind )
974
+ . withName ( this . convertName ( 'ResolversUnionTypes' ) )
975
+ . withComment ( 'Mapping of union types' )
976
+ . withBlock (
977
+ Object . entries ( this . _resolversUnionTypes )
978
+ . map ( ( [ typeName , value ] ) => indent ( `${ typeName } : ${ value } ${ this . getPunctuation ( declarationKind ) } ` ) )
979
+ . join ( '\n' )
980
+ ) . string ;
981
+ }
982
+
938
983
public get schema ( ) : GraphQLSchema {
939
984
return this . _schema ;
940
985
}
@@ -1478,6 +1523,55 @@ export class BaseResolversVisitor<
1478
1523
SchemaDefinition ( ) {
1479
1524
return null ;
1480
1525
}
1526
+
1527
+ private getRelevantFieldsToOmit ( {
1528
+ schemaType,
1529
+ shouldInclude,
1530
+ getTypeToUse,
1531
+ } : {
1532
+ schemaType : GraphQLObjectType ;
1533
+ getTypeToUse : ( name : string ) => string ;
1534
+ shouldInclude ?: ( type : GraphQLNamedType ) => boolean ;
1535
+ } ) : {
1536
+ addOptionalSign : boolean ;
1537
+ fieldName : string ;
1538
+ replaceWithType : string ;
1539
+ } [ ] {
1540
+ const fields = schemaType . getFields ( ) ;
1541
+ return this . _federation
1542
+ . filterFieldNames ( Object . keys ( fields ) )
1543
+ . filter ( fieldName => {
1544
+ const field = fields [ fieldName ] ;
1545
+ const baseType = getBaseType ( field . type ) ;
1546
+
1547
+ // Filter out fields of types that are not included
1548
+ if ( shouldInclude && ! shouldInclude ( baseType ) ) {
1549
+ return false ;
1550
+ }
1551
+ return true ;
1552
+ } )
1553
+ . map ( fieldName => {
1554
+ const field = fields [ fieldName ] ;
1555
+ const baseType = getBaseType ( field . type ) ;
1556
+ const isUnion = isUnionType ( baseType ) ;
1557
+
1558
+ if ( ! this . config . mappers [ baseType . name ] && ! isUnion && ! this . _shouldMapType [ baseType . name ] ) {
1559
+ return null ;
1560
+ }
1561
+
1562
+ const addOptionalSign = ! this . config . avoidOptionals && ! isNonNullType ( field . type ) ;
1563
+
1564
+ return {
1565
+ addOptionalSign,
1566
+ fieldName,
1567
+ replaceWithType : wrapTypeWithModifiers ( getTypeToUse ( baseType . name ) , field . type , {
1568
+ wrapOptional : this . applyMaybe ,
1569
+ wrapArray : this . wrapWithArray ,
1570
+ } ) ,
1571
+ } ;
1572
+ } )
1573
+ . filter ( a => a ) ;
1574
+ }
1481
1575
}
1482
1576
1483
1577
function replacePlaceholder ( pattern : string , typename : string ) : string {
0 commit comments