Skip to content

Commit

Permalink
2nd take
Browse files Browse the repository at this point in the history
  • Loading branch information
ardatan committed May 20, 2024
1 parent 252e800 commit a6c876e
Show file tree
Hide file tree
Showing 14 changed files with 197 additions and 47 deletions.
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: 6 additions & 3 deletions packages/batch-delegate/src/getLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ 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) as GraphQLOutputType,
),
onLocatedError: originalError => {
if (originalError.path == null) {
return originalError;
Expand Down Expand Up @@ -82,13 +84,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
8 changes: 6 additions & 2 deletions packages/delegate/src/mergeFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,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
30 changes: 29 additions & 1 deletion 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 @@ -199,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 @@ -487,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
49 changes: 48 additions & 1 deletion packages/federation/src/supergraph.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
buildASTSchema,
DefinitionNode,
DocumentNode,
EnumTypeDefinitionNode,
EnumValueDefinitionNode,
Expand All @@ -10,7 +11,9 @@ import {
InputValueDefinitionNode,
InterfaceTypeDefinitionNode,
InterfaceTypeExtensionNode,
isInputObjectType,
isInterfaceType,
isObjectType,
Kind,
NamedTypeNode,
ObjectTypeDefinitionNode,
Expand All @@ -29,6 +32,7 @@ import {
visitWithTypeInfo,
} from 'graphql';
import {
defaultMergedResolver,
delegateToSchema,
extractUnavailableFieldsFromSelectionSet,
MergedTypeConfig,
Expand Down Expand Up @@ -1077,10 +1081,53 @@ export function getStitchedSchemaFromSupergraphSdl(opts: GetSubschemasFromSuperg
fieldConfigMerger: getFieldMergerFromSupergraphSdl(opts.supergraphSdl),
},
});
const extraDefinitions: DefinitionNode[] = [];
for (const definition of ensureSupergraphSDLAst(opts.supergraphSdl).definitions) {
if ('fields' in definition && definition.fields) {
const typeInSchema = supergraphSchema.getType(definition.name.value);
if (!typeInSchema || !('getFields' in typeInSchema)) {
extraDefinitions.push(definition);
} else {
const fieldsInSchema = typeInSchema.getFields();
const extraFields: (FieldDefinitionNode | InputValueDefinitionNode)[] = [];
for (const field of definition.fields) {
if (!fieldsInSchema[field.name.value]) {
extraFields.push(field);
}
}
if (extraFields.length) {
let definitionKind: Kind | undefined;
if (isObjectType(typeInSchema)) {
definitionKind = Kind.OBJECT_TYPE_DEFINITION;
} else if (isInterfaceType(typeInSchema)) {
definitionKind = Kind.INTERFACE_TYPE_DEFINITION;
} else if (isInputObjectType(typeInSchema)) {
definitionKind = Kind.INPUT_OBJECT_TYPE_DEFINITION;
}
if (definitionKind) {
extraDefinitions.push({
kind: definitionKind,
name: definition.name,
fields: extraFields,
// `fields` are different in input object types and regular types
} as DefinitionNode);
}
}
}
}
}
return filterInternalFieldsAndTypes(
mergeSchemas({
schemas: [supergraphSchema],
typeDefs: [opts.supergraphSdl],
typeDefs: [
{
kind: Kind.DOCUMENT,
definitions: extraDefinitions,
},
],
defaultFieldResolver: defaultMergedResolver,
assumeValid: true,
assumeValidSDL: true,
}),
);
}
Expand Down
4 changes: 3 additions & 1 deletion packages/federation/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ export function getKeyFnForFederation(typeName: string, keys: string[]) {
return function keyFn(root: any) {
return allKeyProps.reduce(
(prev, key) => {
prev[key] = root[key];
if (key !== '__typename') {
prev[key] = root[key];
}
return prev;
},
{ __typename: typeName },
Expand Down
2 changes: 2 additions & 0 deletions packages/schema/src/makeExecutableSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export function makeExecutableSchema<TContext = any>({
inheritResolversFromInterfaces = false,
updateResolversInPlace = false,
schemaExtensions,
defaultFieldResolver,
...otherOptions
}: IExecutableSchemaDefinition<TContext>) {
// Validate and clean up arguments
Expand Down Expand Up @@ -94,6 +95,7 @@ export function makeExecutableSchema<TContext = any>({
resolverValidationOptions,
inheritResolversFromInterfaces,
updateResolversInPlace,
defaultFieldResolver,
});

if (Object.keys(resolverValidationOptions).length > 0) {
Expand Down
6 changes: 5 additions & 1 deletion packages/schema/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BuildSchemaOptions, GraphQLSchema } from 'graphql';
import { BuildSchemaOptions, GraphQLFieldResolver, GraphQLSchema } from 'graphql';
import {
GraphQLParseOptions,
IResolvers,
Expand Down Expand Up @@ -42,4 +42,8 @@ export interface IExecutableSchemaDefinition<TContext = any>
* Schema extensions
*/
schemaExtensions?: SchemaExtensions | Array<SchemaExtensions>;
/**
* Default field resolver
*/
defaultFieldResolver?: GraphQLFieldResolver<any, TContext>;
}
78 changes: 51 additions & 27 deletions packages/stitch/src/createDelegationPlanBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
InlineFragmentNode,
isAbstractType,
Kind,
print,
SelectionNode,
SelectionSetNode,
} from 'graphql';
Expand Down Expand Up @@ -276,40 +275,65 @@ export function createDelegationPlanBuilder(mergedTypeInfo: MergedTypeInfo): Del
isAbstractType(typeInSubschema) &&
fieldsNotInSubschema.some(fieldNode => fieldNode.name.value === '__typename')
) {
const subschemaWithTypeName = targetSubschemas.find(subschema => {
const typeInTargetSubSchema = mergedTypeInfo.typeMaps.get(subschema)?.[typeName];
return (
isAbstractType(typeInTargetSubSchema) &&
subschema.transformedSchema.getPossibleTypes(typeInTargetSubSchema).length &&
delegationMaps.every(delegationMap => !delegationMap.has(subschema))
);
});
if (subschemaWithTypeName) {
const delegationStageToFetchTypeName: Map<Subschema, SelectionSetNode> = new Map();
const inlineFragments: InlineFragmentNode[] = [];
for (const fieldNode of fieldNodes) {
if (fieldNode.selectionSet) {
for (const selection of fieldNode.selectionSet.selections) {
if (selection.kind === Kind.INLINE_FRAGMENT) {
inlineFragments.push(selection);
}
const inlineFragments: InlineFragmentNode[] = [];
for (const fieldNode of fieldNodes) {
if (fieldNode.selectionSet) {
for (const selection of fieldNode.selectionSet.selections) {
if (selection.kind === Kind.INLINE_FRAGMENT) {
inlineFragments.push(selection);
}
}
}
delegationStageToFetchTypeName.set(subschemaWithTypeName, {
kind: Kind.SELECTION_SET,
selections: [
{
}
const implementedSubschemas = targetSubschemas.filter(subschema => {
const typeInTargetSubschema = mergedTypeInfo.typeMaps.get(subschema)?.[typeName];
return (
isAbstractType(typeInTargetSubschema) &&
subschema.transformedSchema.getPossibleTypes(typeInTargetSubschema).length
);
});
let added = false;
for (const implementedSubgraphs of implementedSubschemas) {
for (const delegationMap of delegationMaps) {
// SelectionNode is not read-only
const existingSelections = delegationMap.get(implementedSubgraphs)
?.selections as SelectionNode[];
if (existingSelections) {
existingSelections.push({
kind: Kind.FIELD,
name: {
kind: Kind.NAME,
value: '__typename',
},
},
...inlineFragments,
],
});
delegationMaps.push(delegationStageToFetchTypeName);
});
existingSelections.push(...inlineFragments);
added = true;
break;
}
if (added) {
break;
}
}
}
if (!added) {
const subschemaWithTypeName = implementedSubschemas[0];
if (subschemaWithTypeName) {
const delegationStageToFetchTypeName: Map<Subschema, SelectionSetNode> = new Map();
delegationStageToFetchTypeName.set(subschemaWithTypeName, {
kind: Kind.SELECTION_SET,
selections: [
{
kind: Kind.FIELD,
name: {
kind: Kind.NAME,
value: '__typename',
},
},
...inlineFragments,
],
});
delegationMaps.push(delegationStageToFetchTypeName);
}
}
}
if (
Expand Down
7 changes: 4 additions & 3 deletions packages/stitch/src/createMergedTypeResolver.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getNamedType, GraphQLList, GraphQLOutputType, OperationTypeNode } from 'graphql';
import { GraphQLList, GraphQLOutputType, OperationTypeNode } from 'graphql';
import { batchDelegateToSchema } from '@graphql-tools/batch-delegate';
import {
delegateToSchema,
Expand All @@ -8,6 +8,7 @@ import {

export function createMergedTypeResolver<TContext extends Record<string, any> = any>(
mergedTypeResolverOptions: MergedTypeResolverOptions,
mergedType: GraphQLOutputType,
): MergedTypeResolver<TContext> | undefined {
const { fieldName, argsFromKeys, valuesFromResults, args } = mergedTypeResolverOptions;

Expand All @@ -19,7 +20,7 @@ export function createMergedTypeResolver<TContext extends Record<string, any> =
subschema,
selectionSet,
key,
type = getNamedType(info.returnType) as GraphQLOutputType,
type = mergedType,
) {
return batchDelegateToSchema({
schema: subschema,
Expand All @@ -46,7 +47,7 @@ export function createMergedTypeResolver<TContext extends Record<string, any> =
subschema,
selectionSet,
_key,
type = getNamedType(info.returnType) as GraphQLOutputType,
type = mergedType,
) {
return delegateToSchema({
schema: subschema,
Expand Down
Loading

0 comments on commit a6c876e

Please sign in to comment.