From 10187dea993d46bc7a639605a901389cb8ef6d9b Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Mon, 7 Jul 2025 11:40:50 +0100 Subject: [PATCH] Add types to simplifyParsedResolveInfoFragmentWithType and fix bug in field merging --- .../graphql-parse-resolve-info/src/index.ts | 60 ++++++++++++++++--- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/packages/graphql-parse-resolve-info/src/index.ts b/packages/graphql-parse-resolve-info/src/index.ts index acbc60fd5..90ef3b6eb 100644 --- a/packages/graphql-parse-resolve-info/src/index.ts +++ b/packages/graphql-parse-resolve-info/src/index.ts @@ -23,8 +23,8 @@ import * as debugFactory from "debug"; type mixed = Record | string | number | boolean | undefined | null; export interface FieldsByTypeName { - [str: string]: { - [str: string]: ResolveTree; + [typeName: string]: { + [responseName: string]: ResolveTree; }; } @@ -350,17 +350,32 @@ function getType( export function simplifyParsedResolveInfoFragmentWithType( parsedResolveInfoFragment: ResolveTree, type: GraphQLType -) { +): ResolveTree & { fields: { [responseName: string]: ResolveTree } } { const { fieldsByTypeName } = parsedResolveInfoFragment; - const fields = {}; + const fields: { [responseName: string]: ResolveTree } = Object.create(null); const strippedType = getNamedType(type); if (isCompositeType(strippedType)) { - Object.assign(fields, fieldsByTypeName[strippedType.name]); + if (fieldsByTypeName[strippedType.name]) { + Object.assign(fields, fieldsByTypeName[strippedType.name]); + } if (strippedType instanceof GraphQLObjectType) { const objectType: GraphQLObjectType = strippedType; - // GraphQL ensures that the subfields cannot clash, so it's safe to simply overwrite them + // GraphQL ensures that the subfields cannot clash, so it's safe to + // simply merge them. for (const anInterface of objectType.getInterfaces()) { - Object.assign(fields, fieldsByTypeName[anInterface.name]); + const interfaceFields = fieldsByTypeName[anInterface.name]; + if (!interfaceFields) continue; + for (const responseName of Object.keys(interfaceFields)) { + const interfaceField = interfaceFields[responseName]; + if (fields[responseName]) { + fields[responseName] = recursiveMerge( + fields[responseName], + interfaceField + ); + } else { + fields[responseName] = interfaceField; + } + } } } } @@ -370,6 +385,37 @@ export function simplifyParsedResolveInfoFragmentWithType( }; } +function recursiveMerge(treeA: ResolveTree, treeB: ResolveTree): ResolveTree { + const result: ResolveTree = { + // GraphQL ensures that the subfields cannot clash, so the name, alias and + // args will definitely line up, and the fieldsByTypeName can be merged. + ...treeA, + fieldsByTypeName: Object.create(null), + }; + + for (const tree of [treeA, treeB]) { + for (const typeName of Object.keys(tree.fieldsByTypeName)) { + if (!result.fieldsByTypeName[typeName]) { + result.fieldsByTypeName[typeName] = Object.create(null); + } + const targetFields = result.fieldsByTypeName[typeName]; + const sourceFields = tree.fieldsByTypeName[typeName]; + for (const responseName of Object.keys(sourceFields)) { + if (!targetFields[responseName]) { + targetFields[responseName] = sourceFields[responseName]; + } else { + targetFields[responseName] = recursiveMerge( + targetFields[responseName], + sourceFields[typeName] + ); + } + } + } + } + + return result; +} + export const parse = parseResolveInfo; export const simplify = simplifyParsedResolveInfoFragmentWithType; export const getAlias = getAliasFromResolveInfo;