Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Federation: Update Tests & Interface Objects 2nd take #6194

Merged
merged 4 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/dry-experts-camp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@graphql-tools/batch-delegate": patch
"@graphql-tools/federation": patch
"@graphql-tools/delegate": patch
"@graphql-tools/schema": patch
"@graphql-tools/stitch": patch
"@graphql-tools/utils": patch
---

Handle interface objects in a different way
9 changes: 5 additions & 4 deletions packages/batch-delegate/src/getLoader.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import DataLoader from 'dataloader';
import { getNamedType, GraphQLList, GraphQLOutputType, GraphQLSchema, print } from 'graphql';
import { getNamedType, GraphQLList, GraphQLSchema, print } from 'graphql';
import { ValueOrPromise } from 'value-or-promise';
import { delegateToSchema, getActualFieldNodes, SubschemaConfig } from '@graphql-tools/delegate';
import { memoize1, memoize2, relocatedError } from '@graphql-tools/utils';
Expand All @@ -13,7 +13,7 @@ function createBatchFn<K = any>(options: BatchDelegateOptions) {
return function batchFn(keys: ReadonlyArray<K>) {
return new ValueOrPromise(() =>
delegateToSchema({
returnType: new GraphQLList(getNamedType(options.info.returnType) as GraphQLOutputType),
returnType: new GraphQLList(getNamedType(options.returnType || options.info.returnType)),
onLocatedError: originalError => {
if (originalError.path == null) {
return originalError;
Expand Down Expand Up @@ -82,13 +82,14 @@ export function getLoader<K = any, V = any, C = K>(
dataLoaderOptions,
fieldNodes = getActualFieldNodes(info.fieldNodes[0]),
selectionSet = fieldNodes[0].selectionSet,
returnType = info.returnType,
} = options;
const loaders = getLoadersMap<K, V, C>(context ?? GLOBAL_CONTEXT, schema);

let cacheKey = fieldName;

if (info.returnType) {
const namedType = getNamedType(info.returnType);
if (returnType) {
const namedType = getNamedType(returnType);
cacheKey += '@' + namedType.name;
}

Expand Down
6 changes: 6 additions & 0 deletions packages/delegate/src/finalizeGatewayRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,12 @@ function finalizeSelectionSet(
if (node.typeCondition != null) {
const parentType = typeInfo.getParentType();
const innerType = schema.getType(node.typeCondition.name.value);
if (
isUnionType(parentType) &&
parentType.getTypes().some(t => t.name === innerType?.name)
) {
return node;
}
if (!implementsAbstractType(schema, parentType, innerType)) {
return null;
}
Expand Down
16 changes: 14 additions & 2 deletions packages/delegate/src/mergeFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
GraphQLObjectType,
GraphQLResolveInfo,
GraphQLSchema,
isAbstractType,
locatedError,
responsePathAsArray,
SelectionSetNode,
Expand Down Expand Up @@ -99,11 +100,15 @@ export function mergeFields<TContext>(
return executeFn();
}, undefined);

function handleDelegationPlanResult() {
return object;
}

if (isPromise(res$)) {
return res$.then(() => object);
return res$.then(handleDelegationPlanResult);
}

return object;
return handleDelegationPlanResult();
}

export function handleResolverResult(
Expand Down Expand Up @@ -149,6 +154,13 @@ export function handleResolverResult(
}
const existingPropValue = object[responseKey];
const sourcePropValue = resolverResult[responseKey];
if (
responseKey === '__typename' &&
existingPropValue !== sourcePropValue &&
isAbstractType(subschema.transformedSchema.getType(sourcePropValue))
) {
continue;
}
if (sourcePropValue != null || existingPropValue == null) {
if (
existingPropValue != null &&
Expand Down
49 changes: 47 additions & 2 deletions packages/delegate/src/prepareGatewayDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
FieldNode,
FragmentDefinitionNode,
getNamedType,
GraphQLNamedOutputType,
GraphQLNamedType,
GraphQLOutputType,
GraphQLSchema,
Expand Down Expand Up @@ -72,6 +73,24 @@ export function prepareGatewayDocument(
});
}
}
const typeInSubschema = transformedSchema.getType(typeName);
if (!typeInSubschema) {
for (const selection of selectionNode.selectionSet.selections) {
newSelections.push(selection);
}
}
if (typeInSubschema && 'getFields' in typeInSubschema) {
const fieldMap = typeInSubschema.getFields();
for (const selection of selectionNode.selectionSet.selections) {
if (selection.kind === Kind.FIELD) {
const fieldName = selection.name.value;
const field = fieldMap[fieldName];
if (!field) {
newSelections.push(selection);
}
}
}
}
}
newSelections.push(selectionNode);
}
Expand Down Expand Up @@ -180,6 +199,12 @@ function visitSelectionSet(
const possibleTypes = possibleTypesMap[selection.typeCondition.name.value];

if (possibleTypes == null) {
const fieldNodesForTypeName = fieldNodesByField[parentTypeName]?.['__typename'];
if (fieldNodesForTypeName) {
for (const fieldNode of fieldNodesForTypeName) {
newSelections.add(fieldNode);
}
}
newSelections.add(selection);
continue;
}
Expand All @@ -193,6 +218,10 @@ function visitSelectionSet(
newSelections.add(generateInlineFragment(possibleTypeName, selection.selectionSet));
}
}

if (possibleTypes.length === 0) {
newSelections.add(selection);
}
}
} else if (selection.kind === Kind.FRAGMENT_SPREAD) {
const fragmentName = selection.name.value;
Expand Down Expand Up @@ -222,17 +251,28 @@ function visitSelectionSet(
} else {
const fieldName = selection.name.value;
let skipAddingDependencyNodes = false;

// TODO: Optimization to prevent extra fields to the subgraph
if (isObjectType(parentType) || isInterfaceType(parentType)) {
if (isAbstractType(parentType)) {
skipAddingDependencyNodes = false;
const fieldNodesForTypeName = fieldNodesByField[parentTypeName]?.['__typename'];
if (fieldNodesForTypeName) {
for (const fieldNode of fieldNodesForTypeName) {
newSelections.add(fieldNode);
}
}
} else if (isObjectType(parentType) || isInterfaceType(parentType)) {
const fieldMap = parentType.getFields();
const field = fieldMap[fieldName];
if (field) {
const unavailableFields = extractUnavailableFields(schema, field, selection, shouldAdd);
skipAddingDependencyNodes = unavailableFields.length === 0;
}
}

if (!skipAddingDependencyNodes) {
const fieldNodes = fieldNodesByField[parentTypeName]?.[fieldName];

if (fieldNodes != null) {
for (const fieldNode of fieldNodes) {
newSelections.add(fieldNode);
Expand Down Expand Up @@ -470,10 +510,15 @@ function wrapConcreteTypes(
if (isLeafType(namedType)) {
return document;
}
const possibleTypes = isAbstractType(namedType)

let possibleTypes: readonly GraphQLNamedOutputType[] = isAbstractType(namedType)
? targetSchema.getPossibleTypes(namedType)
: [namedType];

if (possibleTypes.length === 0) {
possibleTypes = [namedType];
}

const rootTypeNames = getRootTypeNames(targetSchema);

const typeInfo = new TypeInfo(targetSchema);
Expand Down
Loading
Loading