Skip to content

Commit ee24ea6

Browse files
devin-ai-integration[bot]rishabh-fernRishabh
authored
fix(cli): include field-level arguments in GraphQL object type conversion (#16171)
* fix: include field-level arguments in GraphQL object type conversion Co-Authored-By: rishabh <rishabh@buildwithfern.com> * test: update account-schema snapshot with field arguments Co-Authored-By: rishabh <rishabh@buildwithfern.com> * Update new fixes * Add to summary --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: rishabh <rishabh@buildwithfern.com> Co-authored-by: Rishabh <rishabh@Rishabs-Fern-Mac.local>
1 parent cd3bd0a commit ee24ea6

7 files changed

Lines changed: 1233 additions & 216 deletions

File tree

packages/cli/api-importers/graphql/src/GraphQLConverter.ts

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ export class GraphQLConverter {
105105
return true;
106106
}
107107

108+
// NOTE: This heuristic treats a type as a namespace (operation-grouping type) when ALL
109+
// of its fields accept arguments. This can produce a false positive for regular data
110+
// types where every field happens to be parameterized. If that becomes a problem,
111+
// introduce an explicit config option (e.g. namespacedRootTypes: [...]) rather than
112+
// relying on field-arg counts alone.
108113
private isNamespaceType(type: GraphQLObjectType): boolean {
109114
const fields = Object.values(type.getFields());
110115
if (fields.length === 0) {
@@ -113,6 +118,44 @@ export class GraphQLConverter {
113118
return fields.every((f) => f.args.length > 0);
114119
}
115120

121+
// Returns the names of object types that will be consumed as namespace groupings by
122+
// convertOperations — i.e. types that appear as the return type of a zero-arg root
123+
// field whose own fields all accept arguments. These types must be excluded from the
124+
// type registry in collectTypeDefinitions to prevent their fields from showing up
125+
// both as object properties and as standalone operations.
126+
private collectNamespaceTypeNames(): Set<string> {
127+
if (!this.schema) {
128+
return new Set();
129+
}
130+
const namespaceTypeNames = new Set<string>();
131+
const rootTypes: GraphQLObjectType[] = [];
132+
const queryType = this.schema.getQueryType();
133+
if (queryType) {
134+
rootTypes.push(queryType);
135+
}
136+
const mutationType = this.schema.getMutationType();
137+
if (mutationType) {
138+
rootTypes.push(mutationType);
139+
}
140+
const subscriptionType = this.schema.getSubscriptionType();
141+
if (subscriptionType && this.isActualSubscriptionRootType(subscriptionType)) {
142+
rootTypes.push(subscriptionType);
143+
}
144+
for (const rootType of rootTypes) {
145+
for (const field of Object.values(rootType.getFields())) {
146+
const returnRawType = this.unwrapNonNull(field.type);
147+
if (
148+
returnRawType instanceof GraphQLObjectType &&
149+
field.args.length === 0 &&
150+
this.isNamespaceType(returnRawType)
151+
) {
152+
namespaceTypeNames.add(returnRawType.name);
153+
}
154+
}
155+
}
156+
return namespaceTypeNames;
157+
}
158+
116159
public async convert(): Promise<GraphQLConverterResult> {
117160
const sdlContent = await readFile(this.filePath, "utf-8");
118161
this.schema = buildSchema(sdlContent);
@@ -144,6 +187,12 @@ export class GraphQLConverter {
144187
return;
145188
}
146189

190+
// Identify namespace types up-front so we can exclude them below. Namespace types
191+
// have their fields registered as standalone operations via convertNamespaceOperations;
192+
// registering them as type definitions too would make their fields appear in both
193+
// places, causing redundant/confusing output.
194+
const namespaceTypeNames = this.collectNamespaceTypeNames();
195+
147196
const typeMap = this.schema.getTypeMap();
148197
for (const [typeName, type] of Object.entries(typeMap)) {
149198
// Skip built-in types
@@ -163,6 +212,11 @@ export class GraphQLConverter {
163212
continue;
164213
}
165214

215+
// Skip namespace types — their fields are promoted to top-level operations.
216+
if (type instanceof GraphQLObjectType && namespaceTypeNames.has(typeName)) {
217+
continue;
218+
}
219+
166220
if (type instanceof GraphQLScalarType && this.isBuiltInScalar(typeName)) {
167221
continue;
168222
}
@@ -519,13 +573,14 @@ export class GraphQLConverter {
519573

520574
private convertObjectTypeDefinition(type: GraphQLObjectType): FdrAPI.api.v1.register.TypeShape {
521575
const fields = type.getFields();
522-
const properties = Object.entries(fields).map(
523-
([fieldName, field]): FdrAPI.api.v1.register.ObjectProperty => ({
576+
const properties: FdrAPI.api.v1.register.ObjectProperty[] = Object.entries(fields).map(
577+
([fieldName, field]) => ({
524578
key: FdrAPI.PropertyKey(fieldName),
525579
valueType: this.convertOutputType(field.type),
526580
description: field.description ?? undefined,
527581
availability: undefined,
528-
propertyAccess: undefined
582+
propertyAccess: undefined,
583+
arguments: field.args.length > 0 ? field.args.map((arg) => this.convertArgument(arg)) : undefined
529584
})
530585
);
531586

@@ -581,13 +636,14 @@ export class GraphQLConverter {
581636

582637
private convertInterfaceAsObject(type: GraphQLInterfaceType): FdrAPI.api.v1.register.TypeShape {
583638
const fields = type.getFields();
584-
const properties = Object.entries(fields).map(
585-
([fieldName, field]): FdrAPI.api.v1.register.ObjectProperty => ({
639+
const properties: FdrAPI.api.v1.register.ObjectProperty[] = Object.entries(fields).map(
640+
([fieldName, field]) => ({
586641
key: FdrAPI.PropertyKey(fieldName),
587642
valueType: this.convertOutputType(field.type),
588643
description: field.description ?? undefined,
589644
availability: undefined,
590-
propertyAccess: undefined
645+
propertyAccess: undefined,
646+
arguments: field.args.length > 0 ? field.args.map((arg) => this.convertArgument(arg)) : undefined
591647
})
592648
);
593649

@@ -601,8 +657,8 @@ export class GraphQLConverter {
601657

602658
private convertInputObjectTypeDefinition(type: GraphQLInputObjectType): FdrAPI.api.v1.register.TypeShape {
603659
const fields = type.getFields();
604-
const properties = Object.entries(fields).map(
605-
([fieldName, field]): FdrAPI.api.v1.register.ObjectProperty => ({
660+
const properties: FdrAPI.api.v1.register.ObjectProperty[] = Object.entries(fields).map(
661+
([fieldName, field]) => ({
606662
key: FdrAPI.PropertyKey(fieldName),
607663
valueType: this.convertInputType(field.type),
608664
description: field.description ?? undefined,

0 commit comments

Comments
 (0)