Skip to content

Commit 5106abf

Browse files
committed
[resolvers] Ensure __isTypeof is only generated for implementing types (of Interfaces) or Union members (#10283)
* Implement logic to only generate __isTypeOf for implementing types OR union members * Remove unused types * Add changeset * Remove generateInternalResolversIfNeeded * Fix dev tests * Refactor to use parsedSchemaMeta
1 parent 18182e8 commit 5106abf

15 files changed

+204
-106
lines changed

.changeset/angry-lamps-notice.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@graphql-codegen/visitor-plugin-common': major
3+
'@graphql-codegen/typescript-resolvers': major
4+
'@graphql-codegen/plugin-helpers': major
5+
---
6+
7+
BREAKING CHANGES: Do not generate \_\_isTypeOf for non-implementing types or non-union members

dev-test/modules/types.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,6 @@ export type ArticleResolvers<
220220
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
221221
text?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
222222
title?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
223-
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
224223
};
225224

226225
export type CreditCardResolvers<
@@ -241,7 +240,6 @@ export type DonationResolvers<
241240
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
242241
recipient?: Resolver<ResolversTypes['User'], ParentType, ContextType>;
243242
sender?: Resolver<ResolversTypes['User'], ParentType, ContextType>;
244-
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
245243
};
246244

247245
export type MutationResolvers<
@@ -298,7 +296,6 @@ export type UserResolvers<
298296
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
299297
lastName?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
300298
paymentOptions?: Resolver<Maybe<Array<ResolversTypes['PaymentOption']>>, ParentType, ContextType>;
301-
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
302299
};
303300

304301
export type Resolvers<ContextType = any> = {

dev-test/subpath-import/result.d.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,6 @@ export type UserResolvers<
144144
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
145145
password?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
146146
updatedAt?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
147-
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
148147
};
149148

150149
export type MutationResolvers<
@@ -157,7 +156,6 @@ export type MutationResolvers<
157156
FiedContextType,
158157
RequireFields<MutationCreateUserArgs, 'email' | 'name' | 'password'>
159158
>;
160-
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
161159
};
162160

163161
export type Resolvers<ContextType = TestContext> = {

dev-test/test-schema/resolvers-federation.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -166,15 +166,13 @@ export type AddressResolvers<
166166
city?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
167167
lines?: Resolver<ResolversTypes['Lines'], ParentType, ContextType>;
168168
state?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
169-
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
170169
};
171170

172171
export type BookResolvers<
173172
ContextType = any,
174173
ParentType extends ResolversParentTypes['Book'] = ResolversParentTypes['Book']
175174
> = {
176175
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
177-
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
178176
};
179177

180178
export type LinesResolvers<
@@ -183,7 +181,6 @@ export type LinesResolvers<
183181
> = {
184182
line1?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
185183
line2?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
186-
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
187184
};
188185

189186
export type QueryResolvers<
@@ -216,8 +213,6 @@ export type UserResolvers<
216213
GraphQLRecursivePick<FederationType, { address: { city: true; lines: { line2: true } } }>,
217214
ContextType
218215
>;
219-
220-
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
221216
};
222217

223218
export type Resolvers<ContextType = any> = {

dev-test/test-schema/resolvers-root.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,6 @@ export type QueryResolvers<
141141
ParentType extends ResolversParentTypes['Query'] = ResolversParentTypes['Query']
142142
> = {
143143
someDummyField?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
144-
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
145144
};
146145

147146
export type QueryRootResolvers<
@@ -172,7 +171,6 @@ export type UserResolvers<
172171
email?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
173172
id?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
174173
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
175-
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
176174
};
177175

178176
export type Resolvers<ContextType = any> = {

dev-test/test-schema/resolvers-stitching.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@ export type UserResolvers<
162162
email?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
163163
id?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
164164
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
165-
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
166165
};
167166

168167
export type Resolvers<ContextType = any> = {

dev-test/test-schema/resolvers-types.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,6 @@ export type UserResolvers<
148148
email?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
149149
id?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
150150
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
151-
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
152151
};
153152

154153
export type Resolvers<ContextType = any> = {

dev-test/test-schema/typings.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,6 @@ export type UserResolvers<
136136
email?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
137137
id?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
138138
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
139-
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
140139
};
141140

142141
export type Resolvers<ContextType = any> = {

packages/plugins/other/visitor-plugin-common/src/base-resolvers-visitor.ts

Lines changed: 101 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import {
66
DirectiveDefinitionNode,
77
EnumTypeDefinitionNode,
88
FieldDefinitionNode,
9+
GraphQLInterfaceType,
910
GraphQLNamedType,
1011
GraphQLObjectType,
1112
GraphQLSchema,
13+
GraphQLUnionType,
1214
InputValueDefinitionNode,
1315
InterfaceTypeDefinitionNode,
1416
isEnumType,
@@ -33,8 +35,6 @@ import {
3335
ConvertOptions,
3436
DeclarationKind,
3537
EnumValuesMap,
36-
type NormalizedGenerateInternalResolversIfNeededConfig,
37-
type GenerateInternalResolversIfNeededConfig,
3838
NormalizedAvoidOptionalsConfig,
3939
NormalizedScalarsMap,
4040
ParsedEnumValuesMap,
@@ -77,7 +77,6 @@ export interface ParsedResolversConfig extends ParsedConfig {
7777
resolverTypeSuffix: string;
7878
allResolversTypeName: string;
7979
internalResolversPrefix: string;
80-
generateInternalResolversIfNeeded: NormalizedGenerateInternalResolversIfNeededConfig;
8180
directiveResolverMappings: Record<string, string>;
8281
resolversNonOptionalTypename: ResolversNonOptionalTypenameConfig;
8382
avoidCheckingAbstractTypesRecursively: boolean;
@@ -618,15 +617,6 @@ export interface RawResolversConfig extends RawConfig {
618617
* If you are using `mercurius-js`, please set this field to empty string for better compatibility.
619618
*/
620619
internalResolversPrefix?: string;
621-
/**
622-
* @type object
623-
* @default {}
624-
* @description If relevant internal resolvers are set to `true`, the resolver type will only be generated if the right conditions are met.
625-
* Enabling this allows a more correct type generation for the resolvers.
626-
* For example:
627-
* - `__isTypeOf` is generated for implementing types and union members
628-
*/
629-
generateInternalResolversIfNeeded?: GenerateInternalResolversIfNeededConfig;
630620
/**
631621
* @description Makes `__typename` of resolver mappings non-optional without affecting the base types.
632622
* @default false
@@ -705,6 +695,31 @@ export class BaseResolversVisitor<
705695
baseGeneratedTypename?: string;
706696
};
707697
} = {};
698+
protected _parsedSchemaMeta: {
699+
types: {
700+
interface: Record<
701+
string,
702+
{
703+
type: GraphQLInterfaceType;
704+
implementingTypes: Record<string, GraphQLObjectType>;
705+
}
706+
>;
707+
union: Record<
708+
string,
709+
{
710+
type: GraphQLUnionType;
711+
unionMembers: Record<string, GraphQLObjectType>;
712+
}
713+
>;
714+
};
715+
typesWithIsTypeOf: Record<string, true>;
716+
} = {
717+
types: {
718+
interface: {},
719+
union: {},
720+
},
721+
typesWithIsTypeOf: {},
722+
};
708723
protected _collectedDirectiveResolvers: { [key: string]: string } = {};
709724
protected _variablesTransformer: OperationVariablesToObject;
710725
protected _usedMappers: { [key: string]: boolean } = {};
@@ -713,7 +728,6 @@ export class BaseResolversVisitor<
713728
protected _hasReferencedResolversUnionTypes = false;
714729
protected _hasReferencedResolversInterfaceTypes = false;
715730
protected _resolversUnionTypes: Record<string, string> = {};
716-
protected _resolversUnionParentTypes: Record<string, string> = {};
717731
protected _resolversInterfaceTypes: Record<string, string> = {};
718732
protected _rootTypeNames = new Set<string>();
719733
protected _globalDeclarations = new Set<string>();
@@ -779,6 +793,11 @@ export class BaseResolversVisitor<
779793
this.config.namespacedImportName
780794
);
781795

796+
// 1. Parse schema meta at the start once,
797+
// so we can use it in subsequent generate functions
798+
this.parseSchemaMeta();
799+
800+
// 2. Generate types for resolvers
782801
this._resolversTypes = this.createResolversFields({
783802
applyWrapper: type => this.applyResolverTypeWrapper(type),
784803
clearWrapper: type => this.clearResolverTypeWrapper(type),
@@ -1030,22 +1049,20 @@ export class BaseResolversVisitor<
10301049
return {};
10311050
}
10321051

1033-
const allSchemaTypes = this._schema.getTypeMap();
1034-
const typeNames = this._federation.filterTypeNames(Object.keys(allSchemaTypes));
1035-
1036-
const unionTypes = typeNames.reduce<Record<string, string>>((res, typeName) => {
1037-
const schemaType = allSchemaTypes[typeName];
1038-
1039-
if (isUnionType(schemaType)) {
1040-
const { unionMember, excludeTypes } = this.config.resolversNonOptionalTypename;
1041-
res[typeName] = this.getAbstractMembersType({
1042-
typeName,
1043-
memberTypes: schemaType.getTypes(),
1044-
isTypenameNonOptional: unionMember && !excludeTypes?.includes(typeName),
1045-
});
1046-
}
1047-
return res;
1048-
}, {});
1052+
const unionTypes = Object.entries(this._parsedSchemaMeta.types.union).reduce<Record<string, string>>(
1053+
(res, [typeName, { type: schemaType, unionMembers }]) => {
1054+
if (isUnionType(schemaType)) {
1055+
const { unionMember, excludeTypes } = this.config.resolversNonOptionalTypename;
1056+
res[typeName] = this.getAbstractMembersType({
1057+
typeName,
1058+
memberTypes: Object.values(unionMembers),
1059+
isTypenameNonOptional: unionMember && !excludeTypes?.includes(typeName),
1060+
});
1061+
}
1062+
return res;
1063+
},
1064+
{}
1065+
);
10491066

10501067
return unionTypes;
10511068
}
@@ -1055,37 +1072,22 @@ export class BaseResolversVisitor<
10551072
return {};
10561073
}
10571074

1058-
const allSchemaTypes = this._schema.getTypeMap();
1059-
const typeNames = this._federation.filterTypeNames(Object.keys(allSchemaTypes));
1060-
1061-
const interfaceTypes = typeNames.reduce<Record<string, string>>((res, typeName) => {
1062-
const schemaType = allSchemaTypes[typeName];
1063-
1064-
if (isInterfaceType(schemaType)) {
1065-
const allTypesMap = this._schema.getTypeMap();
1066-
const implementingTypes: GraphQLObjectType[] = [];
1067-
1068-
for (const graphqlType of Object.values(allTypesMap)) {
1069-
if (graphqlType instanceof GraphQLObjectType) {
1070-
const allInterfaces = graphqlType.getInterfaces();
1075+
const interfaceTypes = Object.entries(this._parsedSchemaMeta.types.interface).reduce<Record<string, string>>(
1076+
(res, [typeName, { type: schemaType, implementingTypes }]) => {
1077+
if (isInterfaceType(schemaType)) {
1078+
const { interfaceImplementingType, excludeTypes } = this.config.resolversNonOptionalTypename;
10711079

1072-
if (allInterfaces.some(int => int.name === schemaType.name)) {
1073-
implementingTypes.push(graphqlType);
1074-
}
1075-
}
1080+
res[typeName] = this.getAbstractMembersType({
1081+
typeName,
1082+
memberTypes: Object.values(implementingTypes),
1083+
isTypenameNonOptional: interfaceImplementingType && !excludeTypes?.includes(typeName),
1084+
});
10761085
}
10771086

1078-
const { interfaceImplementingType, excludeTypes } = this.config.resolversNonOptionalTypename;
1079-
1080-
res[typeName] = this.getAbstractMembersType({
1081-
typeName,
1082-
memberTypes: implementingTypes,
1083-
isTypenameNonOptional: interfaceImplementingType && !excludeTypes?.includes(typeName),
1084-
});
1085-
}
1086-
1087-
return res;
1088-
}, {});
1087+
return res;
1088+
},
1089+
{}
1090+
);
10891091

10901092
return interfaceTypes;
10911093
}
@@ -1637,6 +1639,46 @@ export class BaseResolversVisitor<
16371639
return contextType;
16381640
}
16391641

1642+
private parseSchemaMeta(): void {
1643+
const allSchemaTypes = this._schema.getTypeMap();
1644+
const typeNames = this._federation.filterTypeNames(Object.keys(allSchemaTypes));
1645+
1646+
for (const typeName of typeNames) {
1647+
const schemaType = allSchemaTypes[typeName];
1648+
1649+
if (isUnionType(schemaType)) {
1650+
this._parsedSchemaMeta.types.union[schemaType.name] = {
1651+
type: schemaType,
1652+
unionMembers: {},
1653+
};
1654+
1655+
const unionMemberTypes = schemaType.getTypes();
1656+
for (const type of unionMemberTypes) {
1657+
this._parsedSchemaMeta.types.union[schemaType.name].unionMembers[type.name] = type;
1658+
this._parsedSchemaMeta.typesWithIsTypeOf[type.name] = true;
1659+
}
1660+
}
1661+
1662+
if (isInterfaceType(schemaType)) {
1663+
this._parsedSchemaMeta.types.interface[schemaType.name] = {
1664+
type: schemaType,
1665+
implementingTypes: {},
1666+
};
1667+
1668+
for (const graphqlType of Object.values(allSchemaTypes)) {
1669+
if (graphqlType instanceof GraphQLObjectType) {
1670+
const allInterfaces = graphqlType.getInterfaces();
1671+
1672+
if (allInterfaces.some(int => int.name === schemaType.name)) {
1673+
this._parsedSchemaMeta.types.interface[schemaType.name].implementingTypes[graphqlType.name] = graphqlType;
1674+
this._parsedSchemaMeta.typesWithIsTypeOf[graphqlType.name] = true;
1675+
}
1676+
}
1677+
}
1678+
}
1679+
}
1680+
}
1681+
16401682
protected applyRequireFields(argsType: string, fields: InputValueDefinitionNode[]): string {
16411683
this._globalDeclarations.add(REQUIRE_FIELDS_TYPE);
16421684
return `RequireFields<${argsType}, ${fields.map(f => `'${f.name.value}'`).join(' | ')}>`;
@@ -1677,7 +1719,7 @@ export class BaseResolversVisitor<
16771719
).value;
16781720
});
16791721

1680-
if (!rootType) {
1722+
if (!rootType && this._parsedSchemaMeta.typesWithIsTypeOf[typeName]) {
16811723
fieldsContent.push(
16821724
indent(
16831725
`${
@@ -1864,25 +1906,14 @@ export class BaseResolversVisitor<
18641906
suffix: this.config.resolverTypeSuffix,
18651907
});
18661908
const declarationKind = 'type';
1867-
const allTypesMap = this._schema.getTypeMap();
1868-
const implementingTypes: string[] = [];
1869-
18701909
const typeName = node.name as any as string;
1910+
const implementingTypes = Object.keys(this._parsedSchemaMeta.types.interface[typeName].implementingTypes);
18711911

18721912
this._collectedResolvers[typeName] = {
18731913
typename: name + '<ContextType>',
18741914
baseGeneratedTypename: name,
18751915
};
18761916

1877-
for (const graphqlType of Object.values(allTypesMap)) {
1878-
if (graphqlType instanceof GraphQLObjectType) {
1879-
const allInterfaces = graphqlType.getInterfaces();
1880-
if (allInterfaces.find(int => int.name === typeName)) {
1881-
implementingTypes.push(graphqlType.name);
1882-
}
1883-
}
1884-
}
1885-
18861917
const parentType = this.getParentTypeToUse(typeName);
18871918

18881919
const genericTypes: string[] = [

packages/plugins/other/visitor-plugin-common/src/types.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,3 @@ export interface CustomDirectivesConfig {
138138
*/
139139
apolloUnmask?: boolean;
140140
}
141-
142-
export interface GenerateInternalResolversIfNeededConfig {}
143-
export type NormalizedGenerateInternalResolversIfNeededConfig = Required<GenerateInternalResolversIfNeededConfig>;

0 commit comments

Comments
 (0)