Skip to content

Commit b281dd6

Browse files
authored
fix(stitch): handle isolated root fields in correct order (#6107)
1 parent 7ef2ad7 commit b281dd6

File tree

5 files changed

+62
-69
lines changed

5 files changed

+62
-69
lines changed

.changeset/itchy-peas-admire.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@graphql-tools/stitch": patch
3+
---
4+
5+
Fix the priority of isolated fields

packages/federation/test/supergraphs.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ describe('Supergraphs', () => {
3535
!getDirective(sortedInputSchema, enumValueConfig, 'inaccessible')?.length,
3636
}),
3737
);
38-
expect(printSchema(sortedSchema).trim()).toBe(printSchema(filteredInputSchema).trim());
38+
expect(printSchema(pruneSchema(sortedSchema)).trim()).toBe(
39+
printSchema(filteredInputSchema).trim(),
40+
);
3941
});
4042
});
4143
});

packages/merge/tests/merge-nodes.spec.ts

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -492,23 +492,5 @@ describe('Merge Nodes', () => {
492492
assertNamedTypeNode(type.fields[1].type);
493493
expect(type.fields[1].type.name.value).toBe('String');
494494
});
495-
496-
it.skip('should remove schema definition', () => {
497-
const type1 = parse(/* GraphQL */ `
498-
schema {
499-
query: Query
500-
}
501-
type Query {
502-
f1: String
503-
}
504-
`);
505-
const type2 = parse(/* GraphQL */ `
506-
type Query {
507-
f2: String
508-
}
509-
`);
510-
const merged = mergeGraphQLNodes([...type1.definitions, ...type2.definitions]);
511-
expect(Object.values(merged).length).toBe(1);
512-
});
513495
});
514496
});

packages/stitch/src/subschemaConfigTransforms/isolateComputedFieldsTransformer.ts

Lines changed: 54 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -92,20 +92,31 @@ export function isolateComputedFieldsTransformer(
9292
const returnTypeMergeConfig = subschemaConfig.merge[type.name];
9393

9494
if (isObjectType(type)) {
95-
if (returnTypeMergeConfig?.selectionSet) {
95+
const returnTypeSelectionSet = returnTypeMergeConfig?.selectionSet;
96+
if (returnTypeSelectionSet) {
9697
// this is a merged type, include the selection set
97-
// TODO: how to handle entryPoints
9898
const keyFieldNames: string[] = [];
99-
if (isObjectType(type)) {
100-
const parsedSelectionSet = parseSelectionSet(returnTypeMergeConfig.selectionSet!);
101-
const keyFields = collectFields(
102-
subschemaConfig.schema,
103-
{},
104-
{},
105-
type,
106-
parsedSelectionSet,
107-
);
108-
keyFieldNames.push(...Array.from(keyFields.fields.keys()));
99+
const parsedSelectionSet = parseSelectionSet(returnTypeSelectionSet);
100+
const keyFields = collectFields(
101+
subschemaConfig.schema,
102+
{},
103+
{},
104+
type,
105+
parsedSelectionSet,
106+
);
107+
keyFieldNames.push(...Array.from(keyFields.fields.keys()));
108+
for (const entryPoint of returnTypeMergeConfig.entryPoints ?? []) {
109+
if (entryPoint.selectionSet) {
110+
const parsedSelectionSet = parseSelectionSet(entryPoint.selectionSet);
111+
const keyFields = collectFields(
112+
subschemaConfig.schema,
113+
{},
114+
{},
115+
type,
116+
parsedSelectionSet,
117+
);
118+
keyFieldNames.push(...Array.from(keyFields.fields.keys()));
119+
}
109120
}
110121

111122
isolatedSchemaTypes[type.name] = {
@@ -128,10 +139,12 @@ export function isolateComputedFieldsTransformer(
128139
const implementingTypeFields = isolatedSchemaTypes[implementingType]?.fields;
129140
if (implementingTypeFields) {
130141
for (const fieldName in implementingTypeFields) {
131-
fields[fieldName] = {
132-
...implementingTypeFields[fieldName],
133-
...fields[fieldName],
134-
};
142+
if (implementingTypeFields[fieldName]) {
143+
fields[fieldName] = {
144+
...implementingTypeFields[fieldName],
145+
...fields[fieldName],
146+
};
147+
}
135148
}
136149
}
137150
}
@@ -191,7 +204,7 @@ function isIsolatedField(
191204
isolatedSchemaTypes: Record<string, ComputedTypeConfig>,
192205
): boolean {
193206
const fieldConfig = isolatedSchemaTypes[typeName]?.fields?.[fieldName];
194-
if (fieldConfig && Object.keys(fieldConfig).length > 0) {
207+
if (fieldConfig) {
195208
return true;
196209
}
197210
return false;
@@ -345,42 +358,37 @@ function filterIsolatedSubschema(subschemaConfig: IsolatedSubschemaInput): Subsc
345358
}
346359
}
347360

348-
const interfaceFields: Record<string, Record<string, boolean>> = {};
349-
for (const typeName in subschemaConfig.merge) {
350-
const type = subschemaConfig.schema.getType(typeName);
351-
if (!type || isObjectType(type)) {
352-
if (!type || !('getInterfaces' in type)) {
353-
throw new Error(`${typeName} expected to have 'getInterfaces' method`);
354-
}
355-
for (const int of type.getInterfaces()) {
356-
const intType = subschemaConfig.schema.getType(int.name);
357-
if (!intType || !('getFields' in intType)) {
358-
throw new Error(`${int.name} expected to have 'getFields' method`);
359-
}
360-
for (const intFieldName in intType.getFields()) {
361-
if (
362-
subschemaConfig.merge[typeName].fields?.[intFieldName] ||
363-
subschemaConfig.merge[typeName].keyFieldNames.includes(intFieldName)
364-
) {
365-
interfaceFields[int.name] = interfaceFields[int.name] || {};
366-
interfaceFields[int.name][intFieldName] = true;
367-
}
368-
}
369-
}
370-
}
371-
}
372-
361+
const typesForInterface: Record<string, string[]> = {};
373362
const filteredSchema = pruneSchema(
374363
filterSchema({
375364
schema: subschemaConfig.schema,
376-
rootFieldFilter: (operation, fieldName, config) =>
377-
operation === 'Query' &&
378-
(rootFields[fieldName] != null || computedFieldTypes[getNamedType(config.type).name]),
365+
rootFieldFilter: (_, fieldName, config) => {
366+
if (rootFields[fieldName]) {
367+
return true;
368+
}
369+
const returnType = getNamedType(config.type);
370+
if (isAbstractType(returnType)) {
371+
const typesForInterface = getImplementingTypes(returnType.name, subschemaConfig.schema);
372+
return typesForInterface.some(t => computedFieldTypes[t] != null);
373+
}
374+
return computedFieldTypes[returnType.name] != null;
375+
},
379376
objectFieldFilter: (typeName, fieldName) =>
380377
subschemaConfig.merge[typeName] == null ||
381378
subschemaConfig.merge[typeName]?.fields?.[fieldName] != null ||
382379
(subschemaConfig.merge[typeName]?.keyFieldNames ?? []).includes(fieldName),
383-
interfaceFieldFilter: (typeName, fieldName) => interfaceFields[typeName]?.[fieldName] != null,
380+
interfaceFieldFilter: (typeName, fieldName) => {
381+
if (!typesForInterface[typeName]) {
382+
typesForInterface[typeName] = getImplementingTypes(typeName, subschemaConfig.schema);
383+
}
384+
const isIsolatedFieldName = typesForInterface[typeName].some(implementingTypeName =>
385+
isIsolatedField(implementingTypeName, fieldName, subschemaConfig.merge),
386+
);
387+
return (
388+
isIsolatedFieldName ||
389+
(subschemaConfig.merge[typeName]?.keyFieldNames ?? []).includes(fieldName)
390+
);
391+
},
384392
}),
385393
{ skipPruning: typ => computedFieldTypes[typ.name] != null },
386394
);

packages/stitch/tests/isolateComputedFieldsTransformer.test.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -465,18 +465,14 @@ describe('isolateComputedFieldsTransformer', () => {
465465
expect(computedQueryTypeFields['_item']).toBeDefined();
466466
expect(computedQueryTypeFields['_giftOptions']).toBeDefined();
467467

468-
/* TODO: Federation missing field issue
469468
const baseGiftOptionsType = baseSchema.transformedSchema.getType(
470469
'GiftOptions',
471470
) as GraphQLObjectType;
472471
expect(baseGiftOptionsType).toBeUndefined();
473-
*/
474472
const baseQueryType = baseSchema.transformedSchema.getType('Query') as GraphQLObjectType;
475473
const baseQueryTypeFields = baseQueryType.getFields();
476474
expect(baseQueryTypeFields['_item']).toBeDefined();
477-
/* TODO: Same with above
478475
expect(baseQueryTypeFields['_giftOptions']).toBeUndefined();
479-
*/
480476
});
481477

482478
it('return type is merged type', () => {

0 commit comments

Comments
 (0)