Skip to content

Commit c0f73ba

Browse files
authored
[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 1b7cbef commit c0f73ba

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;
@@ -584,15 +583,6 @@ export interface RawResolversConfig extends RawConfig {
584583
* If you are using `mercurius-js`, please set this field to empty string for better compatibility.
585584
*/
586585
internalResolversPrefix?: string;
587-
/**
588-
* @type object
589-
* @default {}
590-
* @description If relevant internal resolvers are set to `true`, the resolver type will only be generated if the right conditions are met.
591-
* Enabling this allows a more correct type generation for the resolvers.
592-
* For example:
593-
* - `__isTypeOf` is generated for implementing types and union members
594-
*/
595-
generateInternalResolversIfNeeded?: GenerateInternalResolversIfNeededConfig;
596586
/**
597587
* @description Makes `__typename` of resolver mappings non-optional without affecting the base types.
598588
* @default false
@@ -671,6 +661,31 @@ export class BaseResolversVisitor<
671661
baseGeneratedTypename?: string;
672662
};
673663
} = {};
664+
protected _parsedSchemaMeta: {
665+
types: {
666+
interface: Record<
667+
string,
668+
{
669+
type: GraphQLInterfaceType;
670+
implementingTypes: Record<string, GraphQLObjectType>;
671+
}
672+
>;
673+
union: Record<
674+
string,
675+
{
676+
type: GraphQLUnionType;
677+
unionMembers: Record<string, GraphQLObjectType>;
678+
}
679+
>;
680+
};
681+
typesWithIsTypeOf: Record<string, true>;
682+
} = {
683+
types: {
684+
interface: {},
685+
union: {},
686+
},
687+
typesWithIsTypeOf: {},
688+
};
674689
protected _collectedDirectiveResolvers: { [key: string]: string } = {};
675690
protected _variablesTransformer: OperationVariablesToObject;
676691
protected _usedMappers: { [key: string]: boolean } = {};
@@ -679,7 +694,6 @@ export class BaseResolversVisitor<
679694
protected _hasReferencedResolversUnionTypes = false;
680695
protected _hasReferencedResolversInterfaceTypes = false;
681696
protected _resolversUnionTypes: Record<string, string> = {};
682-
protected _resolversUnionParentTypes: Record<string, string> = {};
683697
protected _resolversInterfaceTypes: Record<string, string> = {};
684698
protected _rootTypeNames = new Set<string>();
685699
protected _globalDeclarations = new Set<string>();
@@ -745,6 +759,11 @@ export class BaseResolversVisitor<
745759
this.config.namespacedImportName
746760
);
747761

762+
// 1. Parse schema meta at the start once,
763+
// so we can use it in subsequent generate functions
764+
this.parseSchemaMeta();
765+
766+
// 2. Generate types for resolvers
748767
this._resolversTypes = this.createResolversFields({
749768
applyWrapper: type => this.applyResolverTypeWrapper(type),
750769
clearWrapper: type => this.clearResolverTypeWrapper(type),
@@ -996,22 +1015,20 @@ export class BaseResolversVisitor<
9961015
return {};
9971016
}
9981017

999-
const allSchemaTypes = this._schema.getTypeMap();
1000-
const typeNames = this._federation.filterTypeNames(Object.keys(allSchemaTypes));
1001-
1002-
const unionTypes = typeNames.reduce<Record<string, string>>((res, typeName) => {
1003-
const schemaType = allSchemaTypes[typeName];
1004-
1005-
if (isUnionType(schemaType)) {
1006-
const { unionMember, excludeTypes } = this.config.resolversNonOptionalTypename;
1007-
res[typeName] = this.getAbstractMembersType({
1008-
typeName,
1009-
memberTypes: schemaType.getTypes(),
1010-
isTypenameNonOptional: unionMember && !excludeTypes?.includes(typeName),
1011-
});
1012-
}
1013-
return res;
1014-
}, {});
1018+
const unionTypes = Object.entries(this._parsedSchemaMeta.types.union).reduce<Record<string, string>>(
1019+
(res, [typeName, { type: schemaType, unionMembers }]) => {
1020+
if (isUnionType(schemaType)) {
1021+
const { unionMember, excludeTypes } = this.config.resolversNonOptionalTypename;
1022+
res[typeName] = this.getAbstractMembersType({
1023+
typeName,
1024+
memberTypes: Object.values(unionMembers),
1025+
isTypenameNonOptional: unionMember && !excludeTypes?.includes(typeName),
1026+
});
1027+
}
1028+
return res;
1029+
},
1030+
{}
1031+
);
10151032

10161033
return unionTypes;
10171034
}
@@ -1021,37 +1038,22 @@ export class BaseResolversVisitor<
10211038
return {};
10221039
}
10231040

1024-
const allSchemaTypes = this._schema.getTypeMap();
1025-
const typeNames = this._federation.filterTypeNames(Object.keys(allSchemaTypes));
1026-
1027-
const interfaceTypes = typeNames.reduce<Record<string, string>>((res, typeName) => {
1028-
const schemaType = allSchemaTypes[typeName];
1029-
1030-
if (isInterfaceType(schemaType)) {
1031-
const allTypesMap = this._schema.getTypeMap();
1032-
const implementingTypes: GraphQLObjectType[] = [];
1033-
1034-
for (const graphqlType of Object.values(allTypesMap)) {
1035-
if (graphqlType instanceof GraphQLObjectType) {
1036-
const allInterfaces = graphqlType.getInterfaces();
1041+
const interfaceTypes = Object.entries(this._parsedSchemaMeta.types.interface).reduce<Record<string, string>>(
1042+
(res, [typeName, { type: schemaType, implementingTypes }]) => {
1043+
if (isInterfaceType(schemaType)) {
1044+
const { interfaceImplementingType, excludeTypes } = this.config.resolversNonOptionalTypename;
10371045

1038-
if (allInterfaces.some(int => int.name === schemaType.name)) {
1039-
implementingTypes.push(graphqlType);
1040-
}
1041-
}
1046+
res[typeName] = this.getAbstractMembersType({
1047+
typeName,
1048+
memberTypes: Object.values(implementingTypes),
1049+
isTypenameNonOptional: interfaceImplementingType && !excludeTypes?.includes(typeName),
1050+
});
10421051
}
10431052

1044-
const { interfaceImplementingType, excludeTypes } = this.config.resolversNonOptionalTypename;
1045-
1046-
res[typeName] = this.getAbstractMembersType({
1047-
typeName,
1048-
memberTypes: implementingTypes,
1049-
isTypenameNonOptional: interfaceImplementingType && !excludeTypes?.includes(typeName),
1050-
});
1051-
}
1052-
1053-
return res;
1054-
}, {});
1053+
return res;
1054+
},
1055+
{}
1056+
);
10551057

10561058
return interfaceTypes;
10571059
}
@@ -1584,6 +1586,46 @@ export class BaseResolversVisitor<
15841586
return contextType;
15851587
}
15861588

1589+
private parseSchemaMeta(): void {
1590+
const allSchemaTypes = this._schema.getTypeMap();
1591+
const typeNames = this._federation.filterTypeNames(Object.keys(allSchemaTypes));
1592+
1593+
for (const typeName of typeNames) {
1594+
const schemaType = allSchemaTypes[typeName];
1595+
1596+
if (isUnionType(schemaType)) {
1597+
this._parsedSchemaMeta.types.union[schemaType.name] = {
1598+
type: schemaType,
1599+
unionMembers: {},
1600+
};
1601+
1602+
const unionMemberTypes = schemaType.getTypes();
1603+
for (const type of unionMemberTypes) {
1604+
this._parsedSchemaMeta.types.union[schemaType.name].unionMembers[type.name] = type;
1605+
this._parsedSchemaMeta.typesWithIsTypeOf[type.name] = true;
1606+
}
1607+
}
1608+
1609+
if (isInterfaceType(schemaType)) {
1610+
this._parsedSchemaMeta.types.interface[schemaType.name] = {
1611+
type: schemaType,
1612+
implementingTypes: {},
1613+
};
1614+
1615+
for (const graphqlType of Object.values(allSchemaTypes)) {
1616+
if (graphqlType instanceof GraphQLObjectType) {
1617+
const allInterfaces = graphqlType.getInterfaces();
1618+
1619+
if (allInterfaces.some(int => int.name === schemaType.name)) {
1620+
this._parsedSchemaMeta.types.interface[schemaType.name].implementingTypes[graphqlType.name] = graphqlType;
1621+
this._parsedSchemaMeta.typesWithIsTypeOf[graphqlType.name] = true;
1622+
}
1623+
}
1624+
}
1625+
}
1626+
}
1627+
}
1628+
15871629
protected applyRequireFields(argsType: string, fields: InputValueDefinitionNode[]): string {
15881630
this._globalDeclarations.add(REQUIRE_FIELDS_TYPE);
15891631
return `RequireFields<${argsType}, ${fields.map(f => `'${f.name.value}'`).join(' | ')}>`;
@@ -1624,7 +1666,7 @@ export class BaseResolversVisitor<
16241666
).value;
16251667
});
16261668

1627-
if (!rootType) {
1669+
if (!rootType && this._parsedSchemaMeta.typesWithIsTypeOf[typeName]) {
16281670
fieldsContent.push(
16291671
indent(
16301672
`${
@@ -1811,25 +1853,14 @@ export class BaseResolversVisitor<
18111853
suffix: this.config.resolverTypeSuffix,
18121854
});
18131855
const declarationKind = 'type';
1814-
const allTypesMap = this._schema.getTypeMap();
1815-
const implementingTypes: string[] = [];
1816-
18171856
const typeName = node.name as any as string;
1857+
const implementingTypes = Object.keys(this._parsedSchemaMeta.types.interface[typeName].implementingTypes);
18181858

18191859
this._collectedResolvers[typeName] = {
18201860
typename: name + '<ContextType>',
18211861
baseGeneratedTypename: name,
18221862
};
18231863

1824-
for (const graphqlType of Object.values(allTypesMap)) {
1825-
if (graphqlType instanceof GraphQLObjectType) {
1826-
const allInterfaces = graphqlType.getInterfaces();
1827-
if (allInterfaces.find(int => int.name === typeName)) {
1828-
implementingTypes.push(graphqlType.name);
1829-
}
1830-
}
1831-
}
1832-
18331864
const parentType = this.getParentTypeToUse(typeName);
18341865

18351866
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)