Skip to content

Commit 55a1e9e

Browse files
authored
[federation][resolvers] Add generateInternalResolversIfNeeded. __resolveReference to generate __resolveReference only when resolvable (#9989)
1 parent 3fd4486 commit 55a1e9e

File tree

6 files changed

+529
-44
lines changed

6 files changed

+529
-44
lines changed

.changeset/fifty-dodos-marry.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@graphql-codegen/visitor-plugin-common': minor
3+
'@graphql-codegen/typescript-resolvers': minor
4+
'@graphql-codegen/plugin-helpers': minor
5+
---
6+
7+
Add `generateInternalResolversIfNeeded` option
8+
9+
This option can be used to generate more correct types for internal resolvers. For example, only generate `__resolveReference` if the federation object has a resolvable `@key`.
10+
11+
In the future, this option can be extended to support other internal resolvers e.g. `__isTypeOf` is only generated for implementing types and union members.

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

+54-15
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ApolloFederation, getBaseType } from '@graphql-codegen/plugin-helpers';
1+
import { ApolloFederation, checkObjectTypeFederationDetails, getBaseType } from '@graphql-codegen/plugin-helpers';
22
import { getRootTypeNames } from '@graphql-tools/utils';
33
import autoBind from 'auto-bind';
44
import {
@@ -33,6 +33,8 @@ import {
3333
ConvertOptions,
3434
DeclarationKind,
3535
EnumValuesMap,
36+
type NormalizedGenerateInternalResolversIfNeededConfig,
37+
type GenerateInternalResolversIfNeededConfig,
3638
NormalizedAvoidOptionalsConfig,
3739
NormalizedScalarsMap,
3840
ParsedEnumValuesMap,
@@ -75,12 +77,20 @@ export interface ParsedResolversConfig extends ParsedConfig {
7577
resolverTypeSuffix: string;
7678
allResolversTypeName: string;
7779
internalResolversPrefix: string;
80+
generateInternalResolversIfNeeded: NormalizedGenerateInternalResolversIfNeededConfig;
7881
onlyResolveTypeForInterfaces: boolean;
7982
directiveResolverMappings: Record<string, string>;
8083
resolversNonOptionalTypename: ResolversNonOptionalTypenameConfig;
8184
}
8285

8386
type FieldDefinitionPrintFn = (parentName: string, avoidResolverOptionals: boolean) => string | null;
87+
export interface RootResolver {
88+
content: string;
89+
generatedResolverTypes: {
90+
resolversMap: { name: string };
91+
userDefined: Record<string, { name: string; federation?: { hasResolveReference: boolean } }>;
92+
};
93+
}
8494

8595
export interface RawResolversConfig extends RawConfig {
8696
/**
@@ -570,6 +580,16 @@ export interface RawResolversConfig extends RawConfig {
570580
* If you are using `mercurius-js`, please set this field to empty string for better compatibility.
571581
*/
572582
internalResolversPrefix?: string;
583+
/**
584+
* @type object
585+
* @default { __resolveReference: false }
586+
* @description If relevant internal resolvers are set to `true`, the resolver type will only be generated if the right conditions are met.
587+
* Enabling this allows a more correct type generation for the resolvers.
588+
* For example:
589+
* - `__isTypeOf` is generated for implementing types and union members
590+
* - `__resolveReference` is generated for federation types that have at least one resolvable `@key` directive
591+
*/
592+
generateInternalResolversIfNeeded?: GenerateInternalResolversIfNeededConfig;
573593
/**
574594
* @type boolean
575595
* @default false
@@ -641,7 +661,12 @@ export class BaseResolversVisitor<
641661
> extends BaseVisitor<TRawConfig, TPluginConfig> {
642662
protected _parsedConfig: TPluginConfig;
643663
protected _declarationBlockConfig: DeclarationBlockConfig = {};
644-
protected _collectedResolvers: { [key: string]: { typename: string; baseGeneratedTypename?: string } } = {};
664+
protected _collectedResolvers: {
665+
[key: string]: {
666+
typename: string;
667+
baseGeneratedTypename?: string;
668+
};
669+
} = {};
645670
protected _collectedDirectiveResolvers: { [key: string]: string } = {};
646671
protected _variablesTransformer: OperationVariablesToObject;
647672
protected _usedMappers: { [key: string]: boolean } = {};
@@ -656,7 +681,6 @@ export class BaseResolversVisitor<
656681
protected _globalDeclarations = new Set<string>();
657682
protected _federation: ApolloFederation;
658683
protected _hasScalars = false;
659-
protected _hasFederation = false;
660684
protected _fieldContextTypeMap: FieldContextTypeMap;
661685
protected _directiveContextTypesMap: FieldContextTypeMap;
662686
protected _checkedTypesWithNestedAbstractTypes: Record<string, { checkStatus: 'yes' | 'no' | 'checking' }> = {};
@@ -696,6 +720,9 @@ export class BaseResolversVisitor<
696720
mappers: transformMappers(rawConfig.mappers || {}, rawConfig.mapperTypeSuffix),
697721
scalars: buildScalarsFromConfig(_schema, rawConfig, defaultScalars),
698722
internalResolversPrefix: getConfigValue(rawConfig.internalResolversPrefix, '__'),
723+
generateInternalResolversIfNeeded: {
724+
__resolveReference: rawConfig.generateInternalResolversIfNeeded?.__resolveReference ?? false,
725+
},
699726
resolversNonOptionalTypename: normalizeResolversNonOptionalTypename(
700727
getConfigValue(rawConfig.resolversNonOptionalTypename, false)
701728
),
@@ -1269,21 +1296,15 @@ export class BaseResolversVisitor<
12691296
}
12701297

12711298
public hasFederation(): boolean {
1272-
return this._hasFederation;
1299+
return Object.keys(this._federation.getMeta()).length > 0;
12731300
}
12741301

1275-
public getRootResolver(): {
1276-
content: string;
1277-
generatedResolverTypes: {
1278-
resolversMap: { name: string };
1279-
userDefined: Record<string, { name: string }>;
1280-
};
1281-
} {
1302+
public getRootResolver(): RootResolver {
12821303
const name = this.convertName(this.config.allResolversTypeName);
12831304
const declarationKind = 'type';
12841305
const contextType = `<ContextType = ${this.config.contextType.type}>`;
12851306

1286-
const userDefinedTypes: Record<string, { name: string }> = {};
1307+
const userDefinedTypes: RootResolver['generatedResolverTypes']['userDefined'] = {};
12871308
const content = [
12881309
new DeclarationBlock(this._declarationBlockConfig)
12891310
.export()
@@ -1295,7 +1316,14 @@ export class BaseResolversVisitor<
12951316
const resolverType = this._collectedResolvers[schemaTypeName];
12961317

12971318
if (resolverType.baseGeneratedTypename) {
1298-
userDefinedTypes[schemaTypeName] = { name: resolverType.baseGeneratedTypename };
1319+
userDefinedTypes[schemaTypeName] = {
1320+
name: resolverType.baseGeneratedTypename,
1321+
};
1322+
1323+
const federationMeta = this._federation.getMeta()[schemaTypeName];
1324+
if (federationMeta) {
1325+
userDefinedTypes[schemaTypeName].federation = federationMeta;
1326+
}
12991327
}
13001328

13011329
return indent(this.formatRootResolver(schemaTypeName, resolverType.typename, declarationKind));
@@ -1480,9 +1508,20 @@ export class BaseResolversVisitor<
14801508
};
14811509

14821510
if (this._federation.isResolveReferenceField(node)) {
1483-
this._hasFederation = true;
1484-
signature.type = 'ReferenceResolver';
1511+
if (this.config.generateInternalResolversIfNeeded.__resolveReference) {
1512+
const federationDetails = checkObjectTypeFederationDetails(
1513+
parentType.astNode as ObjectTypeDefinitionNode,
1514+
this._schema
1515+
);
1516+
1517+
if (!federationDetails || federationDetails.resolvableKeyDirectives.length === 0) {
1518+
return '';
1519+
}
1520+
signature.modifier = ''; // if a federation type has resolvable @key, then it should be required
1521+
}
14851522

1523+
this._federation.setMeta(parentType.name, { hasResolveReference: true });
1524+
signature.type = 'ReferenceResolver';
14861525
if (signature.genericTypes.length >= 3) {
14871526
signature.genericTypes = signature.genericTypes.slice(0, 3);
14881527
}

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

+5
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,8 @@ export interface ResolversNonOptionalTypenameConfig {
127127
interfaceImplementingType?: boolean;
128128
excludeTypes?: string[];
129129
}
130+
131+
export interface GenerateInternalResolversIfNeededConfig {
132+
__resolveReference?: boolean;
133+
}
134+
export type NormalizedGenerateInternalResolversIfNeededConfig = Required<GenerateInternalResolversIfNeededConfig>;

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

+2-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
PluginFunction,
66
Types,
77
} from '@graphql-codegen/plugin-helpers';
8-
import { parseMapper } from '@graphql-codegen/visitor-plugin-common';
8+
import { parseMapper, type RootResolver } from '@graphql-codegen/visitor-plugin-common';
99
import { GraphQLSchema } from 'graphql';
1010
import { TypeScriptResolversPluginConfig } from './config.js';
1111
import { TypeScriptResolversVisitor } from './visitor.js';
@@ -15,10 +15,7 @@ const capitalize = (s: string): string => s.charAt(0).toUpperCase() + s.slice(1)
1515
export const plugin: PluginFunction<
1616
TypeScriptResolversPluginConfig,
1717
Types.ComplexPluginOutput<{
18-
generatedResolverTypes: {
19-
resolversMap: { name: string };
20-
userDefined: Record<string, { name: string }>;
21-
};
18+
generatedResolverTypes: RootResolver['generatedResolverTypes'];
2219
}>
2320
> = (schema: GraphQLSchema, documents: Types.DocumentFile[], config: TypeScriptResolversPluginConfig) => {
2421
const imports = [];

0 commit comments

Comments
 (0)