Skip to content

Commit ba3d434

Browse files
authored
[resolvers][federation] Fix federation @requires type (#10366)
* Update test setup * Implement @requires combination * Add changeset * Force release alpha * Fix issue with empty array, set up tests * Generate FederationReferenceTypes once * Update tests related to FederationReferenceTypes * Update dev-tests * Revert force release * Update test related to mapper
1 parent c24a614 commit ba3d434

File tree

9 files changed

+458
-232
lines changed

9 files changed

+458
-232
lines changed

.changeset/small-fans-cross.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': patch
3+
'@graphql-codegen/typescript-resolvers': patch
4+
'@graphql-codegen/plugin-helpers': patch
5+
---
6+
7+
Update @requires type

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

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,15 @@ export type FederationTypes = {
133133
User: User;
134134
};
135135

136+
/** Mapping of federation reference types */
137+
export type FederationReferenceTypes = {
138+
User: { __typename: 'User' } & (
139+
| GraphQLRecursivePick<FederationTypes['User'], { id: true }>
140+
| GraphQLRecursivePick<FederationTypes['User'], { name: true }>
141+
) &
142+
({} | GraphQLRecursivePick<FederationTypes['User'], { address: { city: true; lines: { line2: true } } }>);
143+
};
144+
136145
/** Mapping between all available schema types and the resolvers types */
137146
export type ResolversTypes = {
138147
Address: ResolverTypeWrapper<Address>;
@@ -154,13 +163,7 @@ export type ResolversParentTypes = {
154163
ID: Scalars['ID']['output'];
155164
Lines: Lines;
156165
Query: {};
157-
User:
158-
| User
159-
| ({ __typename: 'User' } & (
160-
| GraphQLRecursivePick<FederationTypes['User'], { id: true }>
161-
| GraphQLRecursivePick<FederationTypes['User'], { name: true }>
162-
) &
163-
GraphQLRecursivePick<FederationTypes['User'], { address: { city: true; lines: { line2: true } } }>);
166+
User: User | FederationReferenceTypes['User'];
164167
Int: Scalars['Int']['output'];
165168
Boolean: Scalars['Boolean']['output'];
166169
};
@@ -199,15 +202,11 @@ export type QueryResolvers<
199202
export type UserResolvers<
200203
ContextType = any,
201204
ParentType extends ResolversParentTypes['User'] = ResolversParentTypes['User'],
202-
FederationType extends FederationTypes['User'] = FederationTypes['User']
205+
FederationReferenceType extends FederationReferenceTypes['User'] = FederationReferenceTypes['User']
203206
> = {
204207
__resolveReference?: ReferenceResolver<
205-
Maybe<ResolversTypes['User']>,
206-
{ __typename: 'User' } & (
207-
| GraphQLRecursivePick<FederationType, { id: true }>
208-
| GraphQLRecursivePick<FederationType, { name: true }>
209-
) &
210-
GraphQLRecursivePick<FederationType, { address: { city: true; lines: { line2: true } } }>,
208+
Maybe<ResolversTypes['User']> | FederationReferenceType,
209+
FederationReferenceType,
211210
ContextType
212211
>;
213212
email?: Resolver<ResolversTypes['String'], ParentType, ContextType>;

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

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -818,12 +818,8 @@ export class BaseResolversVisitor<
818818
shouldInclude: namedType => !isEnumType(namedType),
819819
onNotMappedObjectType: ({ typeName, initialType }) => {
820820
let result = initialType;
821-
const federationReferenceTypes = this._federation.printReferenceSelectionSets({
822-
typeName,
823-
baseFederationType: `${this.convertName('FederationTypes')}['${typeName}']`,
824-
});
825-
if (federationReferenceTypes) {
826-
result += ` | ${federationReferenceTypes}`;
821+
if (this._federation.getMeta()[typeName]?.referenceSelectionSetsString) {
822+
result += ` | ${this.convertName('FederationReferenceTypes')}['${typeName}']`;
827823
}
828824
return result;
829825
},
@@ -1306,6 +1302,33 @@ export class BaseResolversVisitor<
13061302
).string;
13071303
}
13081304

1305+
public buildFederationReferenceTypes(): string {
1306+
const federationMeta = this._federation.getMeta();
1307+
1308+
if (Object.keys(federationMeta).length === 0) {
1309+
return '';
1310+
}
1311+
1312+
const declarationKind = 'type';
1313+
return new DeclarationBlock(this._declarationBlockConfig)
1314+
.export()
1315+
.asKind(declarationKind)
1316+
.withName(this.convertName('FederationReferenceTypes'))
1317+
.withComment('Mapping of federation reference types')
1318+
.withBlock(
1319+
Object.entries(federationMeta)
1320+
.map(([typeName, { referenceSelectionSetsString }]) => {
1321+
if (!referenceSelectionSetsString) {
1322+
return undefined;
1323+
}
1324+
1325+
return indent(`${typeName}: ${referenceSelectionSetsString}${this.getPunctuation(declarationKind)}`);
1326+
})
1327+
.filter(v => v)
1328+
.join('\n')
1329+
).string;
1330+
}
1331+
13091332
public get schema(): GraphQLSchema {
13101333
return this._schema;
13111334
}
@@ -1586,13 +1609,6 @@ export class BaseResolversVisitor<
15861609
}
15871610
}
15881611

1589-
const parentTypeSignature = this._federation.transformFieldParentType({
1590-
fieldNode: original,
1591-
parentType,
1592-
parentTypeSignature: this.getParentTypeForSignature(node),
1593-
federationTypeSignature: 'FederationType',
1594-
});
1595-
15961612
const { mappedTypeKey, resolverType } = ((): { mappedTypeKey: string; resolverType: string } => {
15971613
const baseType = getBaseTypeNode(original.type);
15981614
const realType = baseType.name.value;
@@ -1637,15 +1653,18 @@ export class BaseResolversVisitor<
16371653
name: typeName,
16381654
modifier: avoidResolverOptionals ? '' : '?',
16391655
type: resolverType,
1640-
genericTypes: [mappedTypeKey, parentTypeSignature, contextType, argsType].filter(f => f),
1656+
genericTypes: [mappedTypeKey, this.getParentTypeForSignature(node), contextType, argsType].filter(f => f),
16411657
};
16421658

16431659
if (this._federation.isResolveReferenceField(node)) {
16441660
if (!this._federation.getMeta()[parentType.name].hasResolveReference) {
16451661
return { value: '', meta };
16461662
}
1663+
const resultType = `${mappedTypeKey} | FederationReferenceType`;
1664+
const referenceType = 'FederationReferenceType';
1665+
16471666
signature.type = 'ReferenceResolver';
1648-
signature.genericTypes = [mappedTypeKey, parentTypeSignature, contextType];
1667+
signature.genericTypes = [resultType, referenceType, contextType];
16491668
meta.federation = { isResolveReference: true };
16501669
}
16511670

@@ -1788,7 +1807,7 @@ export class BaseResolversVisitor<
17881807
];
17891808
this._federation.addFederationTypeGenericIfApplicable({
17901809
genericTypes,
1791-
federationTypesType: this.convertName('FederationTypes'),
1810+
federationTypesType: this.convertName('FederationReferenceTypes'),
17921811
typeName,
17931812
});
17941813

@@ -1975,7 +1994,7 @@ export class BaseResolversVisitor<
19751994
];
19761995
this._federation.addFederationTypeGenericIfApplicable({
19771996
genericTypes,
1978-
federationTypesType: this.convertName('FederationTypes'),
1997+
federationTypesType: this.convertName('FederationReferenceTypes'),
19791998
typeName,
19801999
});
19812000

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs
250250
`;
251251

252252
const federationTypes = visitor.buildFederationTypes();
253+
const federationReferenceTypes = visitor.buildFederationReferenceTypes();
253254
const resolversTypeMapping = visitor.buildResolversTypes();
254255
const resolversParentTypeMapping = visitor.buildResolversParentTypes();
255256
const resolversUnionTypesMapping = visitor.buildResolversUnionTypes();
@@ -294,6 +295,7 @@ export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs
294295
content: [
295296
header,
296297
federationTypes,
298+
federationReferenceTypes,
297299
resolversUnionTypesMapping,
298300
resolversInterfaceTypesMapping,
299301
resolversTypeMapping,

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs
167167
) => TResult | Promise<TResult>;
168168

169169

170+
170171
/** Mapping of union types */
171172
export type ResolversUnionTypes<_RefType extends Record<string, unknown>> = ResolversObject<{
172173
ChildUnion:
@@ -433,6 +434,7 @@ export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs
433434
) => TResult | Promise<TResult>;
434435

435436

437+
436438
/** Mapping of union types */
437439
export type ResolversUnionTypes<_RefType extends Record<string, unknown>> = ResolversObject<{
438440
ChildUnion:
@@ -785,6 +787,7 @@ export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs
785787
) => TResult | Promise<TResult>;
786788

787789

790+
788791
/** Mapping of union types */
789792
export type ResolversUnionTypes<_RefType extends Record<string, unknown>> = ResolversObject<{
790793
ChildUnion:

packages/plugins/typescript/resolvers/tests/ts-resolvers.federation.interface.spec.ts

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,19 @@ describe('TypeScript Resolvers Plugin + Apollo Federation - Interface', () => {
124124
Admin: Admin;
125125
};
126126
127+
/** Mapping of federation reference types */
128+
export type FederationReferenceTypes = {
129+
Person:
130+
( { __typename: 'Person' }
131+
& GraphQLRecursivePick<FederationTypes['Person'], {"id":true}> );
132+
User:
133+
( { __typename: 'User' }
134+
& GraphQLRecursivePick<FederationTypes['User'], {"id":true}> );
135+
Admin:
136+
( { __typename: 'Admin' }
137+
& GraphQLRecursivePick<FederationTypes['Admin'], {"id":true}> );
138+
};
139+
127140
128141
/** Mapping of interface types */
129142
export type ResolversInterfaceTypes<_RefType extends Record<string, unknown>> = {
@@ -150,12 +163,8 @@ describe('TypeScript Resolvers Plugin + Apollo Federation - Interface', () => {
150163
Query: {};
151164
Person: ResolversInterfaceTypes<ResolversParentTypes>['Person'];
152165
ID: Scalars['ID']['output'];
153-
User: User |
154-
( { __typename: 'User' }
155-
& GraphQLRecursivePick<FederationTypes['User'], {"id":true}> );
156-
Admin: Admin |
157-
( { __typename: 'Admin' }
158-
& GraphQLRecursivePick<FederationTypes['Admin'], {"id":true}> );
166+
User: User | FederationReferenceTypes['User'];
167+
Admin: Admin | FederationReferenceTypes['Admin'];
159168
Boolean: Scalars['Boolean']['output'];
160169
PersonName: PersonName;
161170
String: Scalars['String']['output'];
@@ -165,26 +174,20 @@ describe('TypeScript Resolvers Plugin + Apollo Federation - Interface', () => {
165174
me?: Resolver<Maybe<ResolversTypes['Person']>, ParentType, ContextType>;
166175
};
167176
168-
export type PersonResolvers<ContextType = any, ParentType extends ResolversParentTypes['Person'] = ResolversParentTypes['Person'], FederationType extends FederationTypes['Person'] = FederationTypes['Person']> = {
177+
export type PersonResolvers<ContextType = any, ParentType extends ResolversParentTypes['Person'] = ResolversParentTypes['Person'], FederationReferenceType extends FederationReferenceTypes['Person'] = FederationReferenceTypes['Person']> = {
169178
__resolveType: TypeResolveFn<'User' | 'Admin', ParentType, ContextType>;
170-
__resolveReference?: ReferenceResolver<Maybe<ResolversTypes['Person']>,
171-
( { __typename: 'Person' }
172-
& GraphQLRecursivePick<FederationType, {"id":true}> ), ContextType>;
179+
__resolveReference?: ReferenceResolver<Maybe<ResolversTypes['Person']> | FederationReferenceType, FederationReferenceType, ContextType>;
173180
};
174181
175-
export type UserResolvers<ContextType = any, ParentType extends ResolversParentTypes['User'] = ResolversParentTypes['User'], FederationType extends FederationTypes['User'] = FederationTypes['User']> = {
176-
__resolveReference?: ReferenceResolver<Maybe<ResolversTypes['User']>,
177-
( { __typename: 'User' }
178-
& GraphQLRecursivePick<FederationType, {"id":true}> ), ContextType>;
182+
export type UserResolvers<ContextType = any, ParentType extends ResolversParentTypes['User'] = ResolversParentTypes['User'], FederationReferenceType extends FederationReferenceTypes['User'] = FederationReferenceTypes['User']> = {
183+
__resolveReference?: ReferenceResolver<Maybe<ResolversTypes['User']> | FederationReferenceType, FederationReferenceType, ContextType>;
179184
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
180185
name?: Resolver<ResolversTypes['PersonName'], ParentType, ContextType>;
181186
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
182187
};
183188
184-
export type AdminResolvers<ContextType = any, ParentType extends ResolversParentTypes['Admin'] = ResolversParentTypes['Admin'], FederationType extends FederationTypes['Admin'] = FederationTypes['Admin']> = {
185-
__resolveReference?: ReferenceResolver<Maybe<ResolversTypes['Admin']>,
186-
( { __typename: 'Admin' }
187-
& GraphQLRecursivePick<FederationType, {"id":true}> ), ContextType>;
189+
export type AdminResolvers<ContextType = any, ParentType extends ResolversParentTypes['Admin'] = ResolversParentTypes['Admin'], FederationReferenceType extends FederationReferenceTypes['Admin'] = FederationReferenceTypes['Admin']> = {
190+
__resolveReference?: ReferenceResolver<Maybe<ResolversTypes['Admin']> | FederationReferenceType, FederationReferenceType, ContextType>;
188191
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
189192
name?: Resolver<ResolversTypes['PersonName'], ParentType, ContextType>;
190193
canImpersonate?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;

packages/plugins/typescript/resolvers/tests/ts-resolvers.federation.mappers.spec.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ describe('TypeScript Resolvers Plugin + Apollo Federation - mappers', () => {
1717
id: ID!
1818
user: User!
1919
}
20+
21+
type Account @key(fields: "id") {
22+
id: ID!
23+
name: String! @external
24+
displayName: String! @requires(fields: "name")
25+
}
2026
`;
2127

2228
const content = await generate({
@@ -25,14 +31,15 @@ describe('TypeScript Resolvers Plugin + Apollo Federation - mappers', () => {
2531
federation: true,
2632
mappers: {
2733
User: './mappers#UserMapper',
34+
Account: './mappers#AccountMapper',
2835
},
2936
},
3037
});
3138

3239
// User should have it
3340
expect(content).toMatchInlineSnapshot(`
3441
"import { GraphQLResolveInfo } from 'graphql';
35-
import { UserMapper } from './mappers';
42+
import { UserMapper, AccountMapper } from './mappers';
3643
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
3744
3845
@@ -115,6 +122,19 @@ describe('TypeScript Resolvers Plugin + Apollo Federation - mappers', () => {
115122
/** Mapping of federation types */
116123
export type FederationTypes = {
117124
User: User;
125+
Account: Account;
126+
};
127+
128+
/** Mapping of federation reference types */
129+
export type FederationReferenceTypes = {
130+
User:
131+
( { __typename: 'User' }
132+
& GraphQLRecursivePick<FederationTypes['User'], {"id":true}> );
133+
Account:
134+
( { __typename: 'Account' }
135+
& GraphQLRecursivePick<FederationTypes['Account'], {"id":true}>
136+
& ( {}
137+
| GraphQLRecursivePick<FederationTypes['Account'], {"name":true}> ) );
118138
};
119139
120140
@@ -126,6 +146,7 @@ describe('TypeScript Resolvers Plugin + Apollo Federation - mappers', () => {
126146
ID: ResolverTypeWrapper<Scalars['ID']['output']>;
127147
String: ResolverTypeWrapper<Scalars['String']['output']>;
128148
UserProfile: ResolverTypeWrapper<Omit<UserProfile, 'user'> & { user: ResolversTypes['User'] }>;
149+
Account: ResolverTypeWrapper<AccountMapper>;
129150
Boolean: ResolverTypeWrapper<Scalars['Boolean']['output']>;
130151
};
131152
@@ -136,17 +157,16 @@ describe('TypeScript Resolvers Plugin + Apollo Federation - mappers', () => {
136157
ID: Scalars['ID']['output'];
137158
String: Scalars['String']['output'];
138159
UserProfile: Omit<UserProfile, 'user'> & { user: ResolversParentTypes['User'] };
160+
Account: AccountMapper;
139161
Boolean: Scalars['Boolean']['output'];
140162
};
141163
142164
export type QueryResolvers<ContextType = any, ParentType extends ResolversParentTypes['Query'] = ResolversParentTypes['Query']> = {
143165
me?: Resolver<Maybe<ResolversTypes['User']>, ParentType, ContextType>;
144166
};
145167
146-
export type UserResolvers<ContextType = any, ParentType extends ResolversParentTypes['User'] = ResolversParentTypes['User'], FederationType extends FederationTypes['User'] = FederationTypes['User']> = {
147-
__resolveReference?: ReferenceResolver<Maybe<ResolversTypes['User']>,
148-
( { __typename: 'User' }
149-
& GraphQLRecursivePick<FederationType, {"id":true}> ), ContextType>;
168+
export type UserResolvers<ContextType = any, ParentType extends ResolversParentTypes['User'] = ResolversParentTypes['User'], FederationReferenceType extends FederationReferenceTypes['User'] = FederationReferenceTypes['User']> = {
169+
__resolveReference?: ReferenceResolver<Maybe<ResolversTypes['User']> | FederationReferenceType, FederationReferenceType, ContextType>;
150170
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
151171
name?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
152172
};
@@ -156,10 +176,17 @@ describe('TypeScript Resolvers Plugin + Apollo Federation - mappers', () => {
156176
user?: Resolver<ResolversTypes['User'], ParentType, ContextType>;
157177
};
158178
179+
export type AccountResolvers<ContextType = any, ParentType extends ResolversParentTypes['Account'] = ResolversParentTypes['Account'], FederationReferenceType extends FederationReferenceTypes['Account'] = FederationReferenceTypes['Account']> = {
180+
__resolveReference?: ReferenceResolver<Maybe<ResolversTypes['Account']> | FederationReferenceType, FederationReferenceType, ContextType>;
181+
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
182+
displayName?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
183+
};
184+
159185
export type Resolvers<ContextType = any> = {
160186
Query?: QueryResolvers<ContextType>;
161187
User?: UserResolvers<ContextType>;
162188
UserProfile?: UserProfileResolvers<ContextType>;
189+
Account?: AccountResolvers<ContextType>;
163190
};
164191
165192
"

0 commit comments

Comments
 (0)