Skip to content

Commit a350117

Browse files
committed
[resolvers][federation] Add __resolveReference to applicable Interface entities, fix Interface types having non-meta resolver fields (#10221)
* Add __resolveReference for applicable Interfaces - Deprecate generateInternalResolversIfNeeded.__resolveReference - Fix tests - Deprecate onlyResolveTypeForInterfaces - Add changeset - Cleanup - Handle __resolveReference generation in Interface - Let FieldDefinition decide whether to generate __resolveReference by checking whether parent has resolvable key * Fix test
1 parent ac75301 commit a350117

File tree

13 files changed

+504
-467
lines changed

13 files changed

+504
-467
lines changed

.changeset/loud-suits-admire.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@graphql-codegen/visitor-plugin-common': major
3+
'@graphql-codegen/typescript-resolvers': major
4+
'@graphql-codegen/plugin-helpers': major
5+
---
6+
7+
Ensure Federation Interfaces have `__resolveReference` if they are resolvable entities
8+
9+
BREAKING CHANGES: Deprecate `onlyResolveTypeForInterfaces` because majority of use cases cannot implement resolvers in Interfaces.
10+
BREAKING CHANGES: Deprecate `generateInternalResolversIfNeeded.__resolveReference` because types do not have `__resolveReference` if they are not Federation entities or are not resolvable. Users should not have to manually set this option. This option was put in to wait for this major version.

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

Lines changed: 71 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ApolloFederation, checkObjectTypeFederationDetails, getBaseType } from '@graphql-codegen/plugin-helpers';
1+
import { ApolloFederation, type FederationMeta, getBaseType } from '@graphql-codegen/plugin-helpers';
22
import { getRootTypeNames } from '@graphql-tools/utils';
33
import autoBind from 'auto-bind';
44
import {
@@ -78,13 +78,15 @@ export interface ParsedResolversConfig extends ParsedConfig {
7878
allResolversTypeName: string;
7979
internalResolversPrefix: string;
8080
generateInternalResolversIfNeeded: NormalizedGenerateInternalResolversIfNeededConfig;
81-
onlyResolveTypeForInterfaces: boolean;
8281
directiveResolverMappings: Record<string, string>;
8382
resolversNonOptionalTypename: ResolversNonOptionalTypenameConfig;
8483
avoidCheckingAbstractTypesRecursively: boolean;
8584
}
8685

87-
type FieldDefinitionPrintFn = (parentName: string, avoidResolverOptionals: boolean) => string | null;
86+
type FieldDefinitionPrintFn = (
87+
parentName: string,
88+
avoidResolverOptionals: boolean
89+
) => { value: string | null; meta: { federation?: { isResolveReference: boolean } } };
8890
export interface RootResolver {
8991
content: string;
9092
generatedResolverTypes: {
@@ -584,20 +586,13 @@ export interface RawResolversConfig extends RawConfig {
584586
internalResolversPrefix?: string;
585587
/**
586588
* @type object
587-
* @default { __resolveReference: false }
589+
* @default {}
588590
* @description If relevant internal resolvers are set to `true`, the resolver type will only be generated if the right conditions are met.
589591
* Enabling this allows a more correct type generation for the resolvers.
590592
* For example:
591593
* - `__isTypeOf` is generated for implementing types and union members
592-
* - `__resolveReference` is generated for federation types that have at least one resolvable `@key` directive
593594
*/
594595
generateInternalResolversIfNeeded?: GenerateInternalResolversIfNeededConfig;
595-
/**
596-
* @type boolean
597-
* @default false
598-
* @description Turning this flag to `true` will generate resolver signature that has only `resolveType` for interfaces, forcing developers to write inherited type resolvers in the type itself.
599-
*/
600-
onlyResolveTypeForInterfaces?: boolean;
601596
/**
602597
* @description Makes `__typename` of resolver mappings non-optional without affecting the base types.
603598
* @default false
@@ -700,7 +695,8 @@ export class BaseResolversVisitor<
700695
rawConfig: TRawConfig,
701696
additionalConfig: TPluginConfig,
702697
private _schema: GraphQLSchema,
703-
defaultScalars: NormalizedScalarsMap = DEFAULT_SCALARS
698+
defaultScalars: NormalizedScalarsMap = DEFAULT_SCALARS,
699+
federationMeta: FederationMeta = {}
704700
) {
705701
super(rawConfig, {
706702
immutableTypes: getConfigValue(rawConfig.immutableTypes, false),
@@ -714,7 +710,6 @@ export class BaseResolversVisitor<
714710
mapOrStr: rawConfig.enumValues,
715711
}),
716712
addUnderscoreToArgsType: getConfigValue(rawConfig.addUnderscoreToArgsType, false),
717-
onlyResolveTypeForInterfaces: getConfigValue(rawConfig.onlyResolveTypeForInterfaces, false),
718713
contextType: parseMapper(rawConfig.contextType || 'any', 'ContextType'),
719714
fieldContextTypes: getConfigValue(rawConfig.fieldContextTypes, []),
720715
directiveContextTypes: getConfigValue(rawConfig.directiveContextTypes, []),
@@ -729,9 +724,7 @@ export class BaseResolversVisitor<
729724
mappers: transformMappers(rawConfig.mappers || {}, rawConfig.mapperTypeSuffix),
730725
scalars: buildScalarsFromConfig(_schema, rawConfig, defaultScalars),
731726
internalResolversPrefix: getConfigValue(rawConfig.internalResolversPrefix, '__'),
732-
generateInternalResolversIfNeeded: {
733-
__resolveReference: rawConfig.generateInternalResolversIfNeeded?.__resolveReference ?? false,
734-
},
727+
generateInternalResolversIfNeeded: {},
735728
resolversNonOptionalTypename: normalizeResolversNonOptionalTypename(
736729
getConfigValue(rawConfig.resolversNonOptionalTypename, false)
737730
),
@@ -740,7 +733,11 @@ export class BaseResolversVisitor<
740733
} as TPluginConfig);
741734

742735
autoBind(this);
743-
this._federation = new ApolloFederation({ enabled: this.config.federation, schema: this.schema });
736+
this._federation = new ApolloFederation({
737+
enabled: this.config.federation,
738+
schema: this.schema,
739+
meta: federationMeta,
740+
});
744741
this._rootTypeNames = getRootTypeNames(_schema);
745742
this._variablesTransformer = new OperationVariablesToObject(
746743
this.scalars,
@@ -1358,7 +1355,9 @@ export class BaseResolversVisitor<
13581355

13591356
const federationMeta = this._federation.getMeta()[schemaTypeName];
13601357
if (federationMeta) {
1361-
userDefinedTypes[schemaTypeName].federation = federationMeta;
1358+
userDefinedTypes[schemaTypeName].federation = {
1359+
hasResolveReference: federationMeta.hasResolveReference,
1360+
};
13621361
}
13631362
}
13641363

@@ -1474,9 +1473,10 @@ export class BaseResolversVisitor<
14741473
const baseType = getBaseTypeNode(original.type);
14751474
const realType = baseType.name.value;
14761475
const parentType = this.schema.getType(parentName);
1476+
const meta: ReturnType<FieldDefinitionPrintFn>['meta'] = {};
14771477

14781478
if (this._federation.skipField({ fieldNode: original, parentType })) {
1479-
return null;
1479+
return { value: null, meta };
14801480
}
14811481

14821482
const contextType = this.getContextType(parentName, node);
@@ -1516,7 +1516,7 @@ export class BaseResolversVisitor<
15161516
}
15171517
}
15181518

1519-
const parentTypeSignature = this._federation.transformParentType({
1519+
const parentTypeSignature = this._federation.transformFieldParentType({
15201520
fieldNode: original,
15211521
parentType,
15221522
parentTypeSignature: this.getParentTypeForSignature(node),
@@ -1545,29 +1545,22 @@ export class BaseResolversVisitor<
15451545
};
15461546

15471547
if (this._federation.isResolveReferenceField(node)) {
1548-
if (this.config.generateInternalResolversIfNeeded.__resolveReference) {
1549-
const federationDetails = checkObjectTypeFederationDetails(
1550-
parentType.astNode as ObjectTypeDefinitionNode,
1551-
this._schema
1552-
);
1553-
1554-
if (!federationDetails || federationDetails.resolvableKeyDirectives.length === 0) {
1555-
return '';
1556-
}
1548+
if (!this._federation.getMeta()[parentType.name].hasResolveReference) {
1549+
return { value: '', meta };
15571550
}
1558-
1559-
this._federation.setMeta(parentType.name, { hasResolveReference: true });
15601551
signature.type = 'ReferenceResolver';
1561-
if (signature.genericTypes.length >= 3) {
1562-
signature.genericTypes = signature.genericTypes.slice(0, 3);
1563-
}
1552+
signature.genericTypes = [mappedTypeKey, parentTypeSignature, contextType];
1553+
meta.federation = { isResolveReference: true };
15641554
}
15651555

1566-
return indent(
1567-
`${signature.name}${signature.modifier}: ${signature.type}<${signature.genericTypes.join(
1568-
', '
1569-
)}>${this.getPunctuation(declarationKind)}`
1570-
);
1556+
return {
1557+
value: indent(
1558+
`${signature.name}${signature.modifier}: ${signature.type}<${signature.genericTypes.join(
1559+
', '
1560+
)}>${this.getPunctuation(declarationKind)}`
1561+
),
1562+
meta,
1563+
};
15711564
};
15721565
}
15731566

@@ -1628,7 +1621,7 @@ export class BaseResolversVisitor<
16281621
(rootType === 'mutation' && this.config.avoidOptionals.mutation) ||
16291622
(rootType === 'subscription' && this.config.avoidOptionals.subscription) ||
16301623
(rootType === false && this.config.avoidOptionals.resolvers)
1631-
);
1624+
).value;
16321625
});
16331626

16341627
if (!rootType) {
@@ -1645,10 +1638,11 @@ export class BaseResolversVisitor<
16451638
`ContextType = ${this.config.contextType.type}`,
16461639
this.transformParentGenericType(parentType),
16471640
];
1648-
if (this._federation.getMeta()[typeName]) {
1649-
const typeRef = `${this.convertName('FederationTypes')}['${typeName}']`;
1650-
genericTypes.push(`FederationType extends ${typeRef} = ${typeRef}`);
1651-
}
1641+
this._federation.addFederationTypeGenericIfApplicable({
1642+
genericTypes,
1643+
federationTypesType: this.convertName('FederationTypes'),
1644+
typeName,
1645+
});
16521646

16531647
const block = new DeclarationBlock(this._declarationBlockConfig)
16541648
.export()
@@ -1837,25 +1831,44 @@ export class BaseResolversVisitor<
18371831
}
18381832

18391833
const parentType = this.getParentTypeToUse(typeName);
1834+
1835+
const genericTypes: string[] = [
1836+
`ContextType = ${this.config.contextType.type}`,
1837+
this.transformParentGenericType(parentType),
1838+
];
1839+
this._federation.addFederationTypeGenericIfApplicable({
1840+
genericTypes,
1841+
federationTypesType: this.convertName('FederationTypes'),
1842+
typeName,
1843+
});
1844+
18401845
const possibleTypes = implementingTypes.map(name => `'${name}'`).join(' | ') || 'null';
1841-
const fields = this.config.onlyResolveTypeForInterfaces ? [] : node.fields || [];
1846+
1847+
// An Interface has __resolveType resolver, and no other fields.
1848+
const blockFields: string[] = [
1849+
indent(
1850+
`${this.config.internalResolversPrefix}resolveType${
1851+
this.config.optionalResolveType ? '?' : ''
1852+
}: TypeResolveFn<${possibleTypes}, ParentType, ContextType>${this.getPunctuation(declarationKind)}`
1853+
),
1854+
];
1855+
1856+
// An Interface in Federation may have the additional __resolveReference resolver, if resolvable.
1857+
// So, we filter out the normal fields declared on the Interface and add the __resolveReference resolver.
1858+
const fields = (node.fields as unknown as FieldDefinitionPrintFn[]).map(f =>
1859+
f(typeName, this.config.avoidOptionals.resolvers)
1860+
);
1861+
for (const field of fields) {
1862+
if (field.meta.federation?.isResolveReference) {
1863+
blockFields.push(field.value);
1864+
}
1865+
}
18421866

18431867
return new DeclarationBlock(this._declarationBlockConfig)
18441868
.export()
18451869
.asKind(declarationKind)
1846-
.withName(name, `<ContextType = ${this.config.contextType.type}, ${this.transformParentGenericType(parentType)}>`)
1847-
.withBlock(
1848-
[
1849-
indent(
1850-
`${this.config.internalResolversPrefix}resolveType${
1851-
this.config.optionalResolveType ? '?' : ''
1852-
}: TypeResolveFn<${possibleTypes}, ParentType, ContextType>${this.getPunctuation(declarationKind)}`
1853-
),
1854-
...(fields as unknown as FieldDefinitionPrintFn[]).map(f =>
1855-
f(typeName, this.config.avoidOptionals.resolvers)
1856-
),
1857-
].join('\n')
1858-
).string;
1870+
.withName(name, `<${genericTypes.join(', ')}>`)
1871+
.withBlock(blockFields.join('\n')).string;
18591872
}
18601873

18611874
SchemaDefinition() {

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,5 @@ export interface CustomDirectivesConfig {
139139
apolloUnmask?: boolean;
140140
}
141141

142-
export interface GenerateInternalResolversIfNeededConfig {
143-
__resolveReference?: boolean;
144-
}
142+
export interface GenerateInternalResolversIfNeededConfig {}
145143
export type NormalizedGenerateInternalResolversIfNeededConfig = Required<GenerateInternalResolversIfNeededConfig>;

packages/plugins/typescript/resolvers/src/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,14 @@ export type Resolver${capitalizedDirectiveName}WithResolve<TResult, TParent, TCo
7575
}
7676
}
7777

78-
const transformedSchema = config.federation ? addFederationReferencesToSchema(schema) : schema;
79-
const visitor = new TypeScriptResolversVisitor({ ...config, directiveResolverMappings }, transformedSchema);
78+
const { transformedSchema, federationMeta } = config.federation
79+
? addFederationReferencesToSchema(schema)
80+
: { transformedSchema: schema, federationMeta: {} };
81+
const visitor = new TypeScriptResolversVisitor(
82+
{ ...config, directiveResolverMappings },
83+
transformedSchema,
84+
federationMeta
85+
);
8086
const namespacedImportPrefix = visitor.config.namespacedImportName ? `${visitor.config.namespacedImportName}.` : '';
8187

8288
const astNode = getCachedDocumentNodeFromSchema(transformedSchema);

packages/plugins/typescript/resolvers/src/visitor.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import { TypeScriptOperationVariablesToObject } from '@graphql-codegen/typescrip
22
import {
33
BaseResolversVisitor,
44
DeclarationKind,
5+
DEFAULT_SCALARS,
56
getConfigValue,
67
normalizeAvoidOptionals,
78
ParsedResolversConfig,
89
} from '@graphql-codegen/visitor-plugin-common';
10+
import type { FederationMeta } from '@graphql-codegen/plugin-helpers';
911
import autoBind from 'auto-bind';
1012
import { EnumTypeDefinitionNode, GraphQLSchema, ListTypeNode, NamedTypeNode, NonNullTypeNode } from 'graphql';
1113
import { TypeScriptResolversPluginConfig } from './config.js';
@@ -24,7 +26,7 @@ export class TypeScriptResolversVisitor extends BaseResolversVisitor<
2426
TypeScriptResolversPluginConfig,
2527
ParsedTypeScriptResolversConfig
2628
> {
27-
constructor(pluginConfig: TypeScriptResolversPluginConfig, schema: GraphQLSchema) {
29+
constructor(pluginConfig: TypeScriptResolversPluginConfig, schema: GraphQLSchema, federationMeta: FederationMeta) {
2830
super(
2931
pluginConfig,
3032
{
@@ -34,7 +36,9 @@ export class TypeScriptResolversVisitor extends BaseResolversVisitor<
3436
allowParentTypeOverride: getConfigValue(pluginConfig.allowParentTypeOverride, false),
3537
optionalInfoArgument: getConfigValue(pluginConfig.optionalInfoArgument, false),
3638
} as ParsedTypeScriptResolversConfig,
37-
schema
39+
schema,
40+
DEFAULT_SCALARS,
41+
federationMeta
3842
);
3943
autoBind(this);
4044
this.setVariablesTransformer(

packages/plugins/typescript/resolvers/tests/__snapshots__/ts-resolvers.spec.ts.snap

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,6 @@ export type SubscriptionResolvers<ContextType = any, ParentType = ResolversParen
272272

273273
export type NodeResolvers<ContextType = any, ParentType = ResolversParentTypes['Node']> = ResolversObject<{
274274
__resolveType: TypeResolveFn<'SomeNode', ParentType, ContextType>;
275-
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
276275
}>;
277276

278277
export type SomeNodeResolvers<ContextType = any, ParentType = ResolversParentTypes['SomeNode']> = ResolversObject<{
@@ -282,19 +281,14 @@ export type SomeNodeResolvers<ContextType = any, ParentType = ResolversParentTyp
282281

283282
export type AnotherNodeResolvers<ContextType = any, ParentType = ResolversParentTypes['AnotherNode']> = ResolversObject<{
284283
__resolveType: TypeResolveFn<'AnotherNodeWithChild' | 'AnotherNodeWithAll', ParentType, ContextType>;
285-
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
286284
}>;
287285

288286
export type WithChildResolvers<ContextType = any, ParentType = ResolversParentTypes['WithChild']> = ResolversObject<{
289287
__resolveType: TypeResolveFn<'AnotherNodeWithChild' | 'AnotherNodeWithAll', ParentType, ContextType>;
290-
unionChild?: Resolver<Maybe<ResolversTypes['ChildUnion']>, ParentType, ContextType>;
291-
node?: Resolver<Maybe<ResolversTypes['AnotherNode']>, ParentType, ContextType>;
292288
}>;
293289

294290
export type WithChildrenResolvers<ContextType = any, ParentType = ResolversParentTypes['WithChildren']> = ResolversObject<{
295291
__resolveType: TypeResolveFn<'AnotherNodeWithAll', ParentType, ContextType>;
296-
unionChildren?: Resolver<Array<ResolversTypes['ChildUnion']>, ParentType, ContextType>;
297-
nodes?: Resolver<Array<ResolversTypes['AnotherNode']>, ParentType, ContextType>;
298292
}>;
299293

300294
export type AnotherNodeWithChildResolvers<ContextType = any, ParentType = ResolversParentTypes['AnotherNodeWithChild']> = ResolversObject<{
@@ -532,7 +526,6 @@ export type SubscriptionResolvers<ContextType = any, ParentType extends Resolver
532526

533527
export type NodeResolvers<ContextType = any, ParentType extends ResolversParentTypes['Node'] = ResolversParentTypes['Node']> = ResolversObject<{
534528
__resolveType: TypeResolveFn<'SomeNode', ParentType, ContextType>;
535-
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
536529
}>;
537530

538531
export type SomeNodeResolvers<ContextType = any, ParentType extends ResolversParentTypes['SomeNode'] = ResolversParentTypes['SomeNode']> = ResolversObject<{
@@ -542,19 +535,14 @@ export type SomeNodeResolvers<ContextType = any, ParentType extends ResolversPar
542535

543536
export type AnotherNodeResolvers<ContextType = any, ParentType extends ResolversParentTypes['AnotherNode'] = ResolversParentTypes['AnotherNode']> = ResolversObject<{
544537
__resolveType: TypeResolveFn<'AnotherNodeWithChild' | 'AnotherNodeWithAll', ParentType, ContextType>;
545-
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
546538
}>;
547539

548540
export type WithChildResolvers<ContextType = any, ParentType extends ResolversParentTypes['WithChild'] = ResolversParentTypes['WithChild']> = ResolversObject<{
549541
__resolveType: TypeResolveFn<'AnotherNodeWithChild' | 'AnotherNodeWithAll', ParentType, ContextType>;
550-
unionChild?: Resolver<Types.Maybe<ResolversTypes['ChildUnion']>, ParentType, ContextType>;
551-
node?: Resolver<Types.Maybe<ResolversTypes['AnotherNode']>, ParentType, ContextType>;
552542
}>;
553543

554544
export type WithChildrenResolvers<ContextType = any, ParentType extends ResolversParentTypes['WithChildren'] = ResolversParentTypes['WithChildren']> = ResolversObject<{
555545
__resolveType: TypeResolveFn<'AnotherNodeWithAll', ParentType, ContextType>;
556-
unionChildren?: Resolver<Array<ResolversTypes['ChildUnion']>, ParentType, ContextType>;
557-
nodes?: Resolver<Array<ResolversTypes['AnotherNode']>, ParentType, ContextType>;
558546
}>;
559547

560548
export type AnotherNodeWithChildResolvers<ContextType = any, ParentType extends ResolversParentTypes['AnotherNodeWithChild'] = ResolversParentTypes['AnotherNodeWithChild']> = ResolversObject<{
@@ -878,7 +866,6 @@ export type SubscriptionResolvers<ContextType = any, ParentType extends Resolver
878866

879867
export type NodeResolvers<ContextType = any, ParentType extends ResolversParentTypes['Node'] = ResolversParentTypes['Node']> = ResolversObject<{
880868
__resolveType: TypeResolveFn<'SomeNode', ParentType, ContextType>;
881-
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
882869
}>;
883870

884871
export type SomeNodeResolvers<ContextType = any, ParentType extends ResolversParentTypes['SomeNode'] = ResolversParentTypes['SomeNode']> = ResolversObject<{
@@ -888,19 +875,14 @@ export type SomeNodeResolvers<ContextType = any, ParentType extends ResolversPar
888875

889876
export type AnotherNodeResolvers<ContextType = any, ParentType extends ResolversParentTypes['AnotherNode'] = ResolversParentTypes['AnotherNode']> = ResolversObject<{
890877
__resolveType: TypeResolveFn<'AnotherNodeWithChild' | 'AnotherNodeWithAll', ParentType, ContextType>;
891-
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
892878
}>;
893879

894880
export type WithChildResolvers<ContextType = any, ParentType extends ResolversParentTypes['WithChild'] = ResolversParentTypes['WithChild']> = ResolversObject<{
895881
__resolveType: TypeResolveFn<'AnotherNodeWithChild' | 'AnotherNodeWithAll', ParentType, ContextType>;
896-
unionChild?: Resolver<Maybe<ResolversTypes['ChildUnion']>, ParentType, ContextType>;
897-
node?: Resolver<Maybe<ResolversTypes['AnotherNode']>, ParentType, ContextType>;
898882
}>;
899883

900884
export type WithChildrenResolvers<ContextType = any, ParentType extends ResolversParentTypes['WithChildren'] = ResolversParentTypes['WithChildren']> = ResolversObject<{
901885
__resolveType: TypeResolveFn<'AnotherNodeWithAll', ParentType, ContextType>;
902-
unionChildren?: Resolver<Array<ResolversTypes['ChildUnion']>, ParentType, ContextType>;
903-
nodes?: Resolver<Array<ResolversTypes['AnotherNode']>, ParentType, ContextType>;
904886
}>;
905887

906888
export type AnotherNodeWithChildResolvers<ContextType = any, ParentType extends ResolversParentTypes['AnotherNodeWithChild'] = ResolversParentTypes['AnotherNodeWithChild']> = ResolversObject<{

0 commit comments

Comments
 (0)