@@ -89,7 +89,12 @@ function parseObjectFields(
8989 weight : typeWeights . scalar ,
9090 // resolveTo: fields[field].name.toLowerCase(),
9191 } ;
92- } else if ( isInterfaceType ( fieldType ) || isEnumType ( fieldType ) || isObjectType ( fieldType ) ) {
92+ } else if (
93+ isInterfaceType ( fieldType ) ||
94+ isEnumType ( fieldType ) ||
95+ isObjectType ( fieldType ) ||
96+ isUnionType ( fieldType )
97+ ) {
9398 result . fields [ field ] = {
9499 resolveTo : fieldType . name . toLocaleLowerCase ( ) ,
95100 } ;
@@ -154,13 +159,6 @@ function parseObjectFields(
154159 }
155160 } ) ;
156161 }
157- } else if ( isUnionType ( fieldType ) ) {
158- // Users must query union types using inline fragments to resolve field specific to one of the types in the union
159- // however, if a type is shared by all types in the union it can be queried outside of the inline fragment
160- // any common fields should be added to fields on the union type itself in addition to the comprising types
161- result . fields [ field ] = {
162- resolveTo : fieldType . name . toLocaleLowerCase ( ) ,
163- } ;
164162 } else if ( isNonNullType ( fieldType ) ) {
165163 // TODO: Implment non-null types
166164 // not throwing and error since it causes typeWeight tests to break
@@ -174,12 +172,14 @@ function parseObjectFields(
174172}
175173
176174/**
177- * Recursively compares two types for type equality based on name
175+ * Recursively compares two types for type equality based on type name
178176 * @param a
179177 * @param b
180- * @returns
178+ * @returns true if the types are recursively equal.
181179 */
182180function compareTypes ( a : GraphQLOutputType , b : GraphQLOutputType ) : boolean {
181+ // Base Case: Object or Scalar => compare type names
182+ // Recursive Case(List / NonNull): compare ofType
183183 return (
184184 ( isObjectType ( b ) && isObjectType ( a ) && a . name === b . name ) ||
185185 ( isUnionType ( b ) && isUnionType ( a ) && a . name === b . name ) ||
@@ -191,110 +191,118 @@ function compareTypes(a: GraphQLOutputType, b: GraphQLOutputType): boolean {
191191}
192192
193193/**
194- * Parses all types in the provided schema object excempt for Query, Mutation
195- * and built in types that begin with '__' and outputs a new TypeWeightObject
196- * @param schema
197- * @param typeWeights
198- * @returns
194+ *
195+ * @param unionType union type to be parsed
196+ * @param typeWeightObject type weight mapping object that must already contain all of the types in the schema.
197+ * @returns object mapping field names for each union type to their respective weights, resolve type names and resolve type object
199198 */
200- function parseTypes ( schema : GraphQLSchema , typeWeights : TypeWeightSet ) : TypeWeightObject {
201- const typeMap : ObjMap < GraphQLNamedType > = schema . getTypeMap ( ) ;
202-
203- const result : TypeWeightObject = { } ;
199+ function getFieldsForUnionType (
200+ unionType : GraphQLUnionType ,
201+ typeWeightObject : TypeWeightObject
202+ ) : FieldMap [ ] {
203+ return unionType . getTypes ( ) . map ( ( objectType : GraphQLObjectType ) => {
204+ // Get the field data for this type
205+ const fields : GraphQLFieldMap < unknown , unknown > = objectType . getFields ( ) ;
204206
205- const unions : GraphQLUnionType [ ] = [ ] ;
207+ const fieldMap : FieldMap = { } ;
208+ Object . keys ( fields ) . forEach ( ( field : string ) => {
209+ // Get the weight of this field on from parent type on the root typeWeight object.
210+ // this only exists for scalars and lists (which resolve to a function);
211+ const { weight, resolveTo } =
212+ typeWeightObject [ objectType . name . toLowerCase ( ) ] . fields [ field ] ;
206213
207- // Handle Object, Interface, Enum and Union types
208- Object . keys ( typeMap ) . forEach ( ( type ) => {
209- const typeName : string = type . toLowerCase ( ) ;
210- const currentType : GraphQLNamedType = typeMap [ type ] ;
214+ fieldMap [ field ] = {
215+ type : fields [ field ] . type ,
216+ weight, // will only be undefined for object types
217+ resolveTo,
218+ } ;
219+ } ) ;
220+ return fieldMap ;
221+ } ) ;
222+ }
211223
212- // Get all types that aren't Query or Mutation or a built in type that starts with '__'
213- if ( ! type . startsWith ( '__' ) ) {
214- if ( isObjectType ( currentType ) || isInterfaceType ( currentType ) ) {
215- // Add the type and it's associated fields to the result
216- result [ typeName ] = parseObjectFields ( currentType , result , typeWeights ) ;
217- } else if ( isEnumType ( currentType ) ) {
218- result [ typeName ] = {
219- fields : { } ,
220- weight : typeWeights . scalar ,
221- } ;
222- } else if ( isUnionType ( currentType ) ) {
223- unions . push ( currentType ) ;
224- } else {
225- // FIXME: Scalar types are listed here throw new Error(`ERROR: buildTypeWeight: Unsupported type: ${currentType}`);
226- // ? what else can get through here
227- // ? inputTypes?
224+ /**
225+ *
226+ * @param typesInUnion
227+ * @returns a single field map containg information for fields common to the union
228+ */
229+ function getSharedFieldsFromUnionTypes ( typesInUnion : FieldMap [ ] ) : FieldMap {
230+ return typesInUnion . reduce ( ( prev : FieldMap , fieldMap : FieldMap ) : FieldMap => {
231+ // iterate through the field map checking the types for any common field names
232+ const sharedFields : FieldMap = { } ;
233+ Object . keys ( prev ) . forEach ( ( field : string ) => {
234+ if ( fieldMap [ field ] ) {
235+ if ( compareTypes ( prev [ field ] . type , fieldMap [ field ] . type ) ) {
236+ // they match add the type to the next set
237+ sharedFields [ field ] = prev [ field ] ;
238+ }
228239 }
229- }
240+ } ) ;
241+ return sharedFields ;
230242 } ) ;
243+ }
231244
232- unions . forEach ( ( unionType : GraphQLUnionType ) => {
233- /** Start with the fields for the first object. Store fieldname, type, weight and resolve to for later use
234- * reduce by selecting fields common to each type
235- * compare both fieldname and output type accounting for lists and non-nulls
236- * for object
237- * compare name of output type
238- * for lists
239- * compare ofType and ofType name if not onother list/non-null
240- * for non-nulls
241- * compare oftype and ofTypeName (if not another non-null)
245+ /**
246+ * Parses the provided union types and returns a type weight object with any fields common to all types
247+ * in a union added to the union type
248+ * @param unionTypes union types to be parsed.
249+ * @param typeWeights object specifying generic type weights.
250+ * @param typeWeightObject original type weight object
251+ * @returns
252+ */
253+ function parseUnionTypes (
254+ unionTypes : GraphQLUnionType [ ] ,
255+ typeWeights : TypeWeightSet ,
256+ typeWeightObject : TypeWeightObject
257+ ) {
258+ const typeWeightsWithUnions : TypeWeightObject = { ...typeWeightObject } ;
259+
260+ unionTypes . forEach ( ( unionType : GraphQLUnionType ) => {
261+ /**
262+ * 1. For each provided union type. We first obtain the fields for each object that
263+ * is part of the union and store these in an object
264+ * When obtaining types, save:
265+ * - field name
266+ * - type object to which the field resolves. This holds any information for recursive types (lists / not null / unions)
267+ * - weight - for easy lookup later
268+ * - resolveTo type - for easy lookup later
269+ * 2. We then reduce the array of objects from step 1 a single object only containing fields
270+ * common to each type in the union. To determine field "equality" we compare the field names and
271+ * recursively compare the field types:
242272 * */
243273
244274 // types is an array mapping each field name to it's respective output type
245- const types : FieldMap [ ] = unionType . getTypes ( ) . map ( ( objectType : GraphQLObjectType ) => {
246- const fields : GraphQLFieldMap < unknown , unknown > = objectType . getFields ( ) ;
247-
248- const fieldMap : FieldMap = { } ;
249- Object . keys ( fields ) . forEach ( ( field : string ) => {
250- // Get the weight of this field on from parent type on the root typeWeight object.
251- // this only exists for scalars and lists (which resolve to a function);
252- const { weight, resolveTo } = result [ objectType . name . toLowerCase ( ) ] . fields [ field ] ;
253-
254- fieldMap [ field ] = {
255- type : fields [ field ] . type ,
256- weight, // will only be undefined for object types
257- resolveTo,
258- } ;
259- } ) ;
260- return fieldMap ;
261- } ) ;
275+ // const typesInUnion = getFieldsForUnionType(unionType, typeWeightObject);
276+ const typesInUnion : FieldMap [ ] = getFieldsForUnionType ( unionType , typeWeightObject ) ;
262277
263- const common : FieldMap = types . reduce ( ( prev : FieldMap , fieldMap : FieldMap ) : FieldMap => {
264- // iterate through the field map checking the types for any common field names
265- const commonFields : FieldMap = { } ;
266- Object . keys ( prev ) . forEach ( ( field : string ) => {
267- if ( fieldMap [ field ] ) {
268- if ( compareTypes ( prev [ field ] . type , fieldMap [ field ] . type ) ) {
269- // they match add the type to the next set
270- commonFields [ field ] = prev [ field ] ;
271- }
272- }
273- } ) ;
274- return commonFields ;
275- } ) ;
278+ // reduce the data for all the types in the union
279+ const commonFields : FieldMap = getSharedFieldsFromUnionTypes ( typesInUnion ) ;
276280
277- // transform commonFields into the correct format
281+ // transform commonFields into the correct format for the type weight object
278282 const fieldTypes : Fields = { } ;
279283
280- Object . keys ( common ) . forEach ( ( field : string ) => {
281- // scalar => weight
282- // list => resolveTo + weight(function)
283- // fields that resolve to objects do not need to appear on the union type
284- const current = common [ field ] . type ;
284+ Object . keys ( commonFields ) . forEach ( ( field : string ) => {
285+ /**
286+ * The type weight object requires that:
287+ * a. scalars have a weight
288+ * b. lists have a resolveTo and weight property
289+ * c. objects have a resolveTo type.
290+ * */
291+
292+ const current = commonFields [ field ] . type ;
285293 if ( isScalarType ( current ) ) {
286294 fieldTypes [ field ] = {
287- weight : common [ field ] . weight ,
295+ weight : commonFields [ field ] . weight ,
288296 } ;
289297 } else if ( isObjectType ( current ) || isInterfaceType ( current ) || isUnionType ( current ) ) {
290298 fieldTypes [ field ] = {
291- resolveTo : common [ field ] . resolveTo ,
299+ resolveTo : commonFields [ field ] . resolveTo ,
292300 weight : typeWeights . object ,
293301 } ;
294302 } else if ( isListType ( current ) ) {
295303 fieldTypes [ field ] = {
296- resolveTo : common [ field ] . resolveTo ,
297- weight : common [ field ] . weight ,
304+ resolveTo : commonFields [ field ] . resolveTo ,
305+ weight : commonFields [ field ] . weight ,
298306 } ;
299307 } else if ( isNonNullType ( current ) ) {
300308 throw new Error ( 'non null types not supported on unions' ) ;
@@ -303,13 +311,52 @@ function parseTypes(schema: GraphQLSchema, typeWeights: TypeWeightSet): TypeWeig
303311 throw new Error ( 'Unhandled union type. Should never get here' ) ;
304312 }
305313 } ) ;
306- result [ unionType . name . toLowerCase ( ) ] = {
314+ typeWeightsWithUnions [ unionType . name . toLowerCase ( ) ] = {
307315 fields : fieldTypes ,
308316 weight : typeWeights . object ,
309317 } ;
310318 } ) ;
311319
312- return result ;
320+ return typeWeightsWithUnions ;
321+ }
322+ /**
323+ * Parses all types in the provided schema object excempt for Query, Mutation
324+ * and built in types that begin with '__' and outputs a new TypeWeightObject
325+ * @param schema
326+ * @param typeWeights
327+ * @returns
328+ */
329+ function parseTypes ( schema : GraphQLSchema , typeWeights : TypeWeightSet ) : TypeWeightObject {
330+ const typeMap : ObjMap < GraphQLNamedType > = schema . getTypeMap ( ) ;
331+
332+ const result : TypeWeightObject = { } ;
333+
334+ const unions : GraphQLUnionType [ ] = [ ] ;
335+
336+ // Handle Object, Interface, Enum and Union types
337+ Object . keys ( typeMap ) . forEach ( ( type ) => {
338+ const typeName : string = type . toLowerCase ( ) ;
339+ const currentType : GraphQLNamedType = typeMap [ type ] ;
340+
341+ // Get all types that aren't Query or Mutation or a built in type that starts with '__'
342+ if ( ! type . startsWith ( '__' ) ) {
343+ if ( isObjectType ( currentType ) || isInterfaceType ( currentType ) ) {
344+ // Add the type and it's associated fields to the result
345+ result [ typeName ] = parseObjectFields ( currentType , result , typeWeights ) ;
346+ } else if ( isEnumType ( currentType ) ) {
347+ result [ typeName ] = {
348+ fields : { } ,
349+ weight : typeWeights . scalar ,
350+ } ;
351+ } else if ( isUnionType ( currentType ) ) {
352+ unions . push ( currentType ) ;
353+ } else if ( ! isScalarType ( currentType ) ) {
354+ throw new Error ( `ERROR: buildTypeWeight: Unsupported type: ${ currentType } ` ) ;
355+ }
356+ }
357+ } ) ;
358+
359+ return parseUnionTypes ( unions , typeWeights , result ) ;
313360}
314361
315362/**
0 commit comments