Skip to content

Commit e567901

Browse files
authored
[typescript-resolvers] Fix resolvers union types used in ResolversParentTypes (#9206)
1 parent 0f53581 commit e567901

File tree

7 files changed

+348
-66
lines changed

7 files changed

+348
-66
lines changed

.changeset/blue-pans-protect.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---
2+
'@graphql-codegen/visitor-plugin-common': patch
3+
'@graphql-codegen/typescript-resolvers': patch
4+
---
5+
6+
Fix `ResolversUnionTypes` being used in `ResolversParentTypes`
7+
8+
Previously, objects with mappable fields are converted to Omit format that references its own type group or `ResolversTypes` or `ResolversParentTypes` e.g.
9+
10+
```ts
11+
export type ResolversTypes = {
12+
Book: ResolverTypeWrapper<BookMapper>;
13+
BookPayload: ResolversTypes["BookResult"] | ResolversTypes["StandardError"];
14+
// Note: `result` on the next line references `ResolversTypes["Book"]`
15+
BookResult: ResolverTypeWrapper<Omit<BookResult, "result"> & { result?: Maybe<ResolversTypes["Book"]> }>;
16+
StandardError: ResolverTypeWrapper<StandardError>;
17+
};
18+
19+
export type ResolversParentTypes = {
20+
Book: BookMapper;
21+
BookPayload: ResolversParentTypes["BookResult"] | ResolversParentTypes["StandardError"];
22+
// Note: `result` on the next line references `ResolversParentTypes["Book"]`
23+
BookResult: Omit<BookResult, "result"> & { result?: Maybe<ResolversParentTypes["Book"]> };
24+
StandardError: StandardError;
25+
};
26+
```
27+
28+
In https://github.com/dotansimha/graphql-code-generator/pull/9069, we extracted resolver union types to its own group:
29+
30+
```ts
31+
export type ResolversUnionTypes = {
32+
// Note: `result` on the next line references `ResolversTypes["Book"]` which is only correct for the `ResolversTypes` case
33+
BookPayload: (Omit<BookResult, "result"> & { result?: Maybe<ResolversTypes["Book"]> }) | StandardError;
34+
};
35+
36+
export type ResolversTypes = {
37+
Book: ResolverTypeWrapper<BookMapper>;
38+
BookPayload: ResolverTypeWrapper<ResolversUnionTypes["BookPayload"]>;
39+
BookResult: ResolverTypeWrapper<Omit<BookResult, "result"> & { result?: Maybe<ResolversTypes["Book"]> }>;
40+
StandardError: ResolverTypeWrapper<StandardError>;
41+
};
42+
43+
export type ResolversParentTypes = {
44+
Book: BookMapper;
45+
BookPayload: ResolversUnionTypes["BookPayload"];
46+
BookResult: Omit<BookResult, "result"> & { result?: Maybe<ResolversParentTypes["Book"]> };
47+
StandardError: StandardError;
48+
};
49+
```
50+
51+
This change creates an extra `ResolversUnionParentTypes` that is referenced by `ResolversParentTypes` to ensure backwards compatibility:
52+
53+
```ts
54+
export type ResolversUnionTypes = {
55+
BookPayload: (Omit<BookResult, "result"> & { result?: Maybe<ResolversParentTypes["Book"]> }) | StandardError;
56+
};
57+
58+
// ... and the reference is changed in ResolversParentTypes:
59+
export type ResolversParentTypes = {
60+
// ... other fields
61+
BookPayload: ResolversUnionParentTypes["BookPayload"];
62+
};
63+
```

dev-test/modules/types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,11 @@ export type ResolversUnionTypes = {
167167
PaymentOption: CreditCard | Paypal;
168168
};
169169

170+
/** Mapping of union parent types */
171+
export type ResolversUnionParentTypes = {
172+
PaymentOption: CreditCard | Paypal;
173+
};
174+
170175
/** Mapping between all available schema types and the resolvers types */
171176
export type ResolversTypes = {
172177
Article: ResolverTypeWrapper<Article>;
@@ -198,7 +203,7 @@ export type ResolversParentTypes = {
198203
ID: Scalars['ID'];
199204
Int: Scalars['Int'];
200205
Mutation: {};
201-
PaymentOption: ResolversUnionTypes['PaymentOption'];
206+
PaymentOption: ResolversUnionParentTypes['PaymentOption'];
202207
Paypal: Paypal;
203208
Query: {};
204209
String: Scalars['String'];

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

Lines changed: 66 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,9 @@ export class BaseResolversVisitor<
568568
protected _resolversTypes: ResolverTypes = {};
569569
protected _resolversParentTypes: ResolverParentTypes = {};
570570
protected _hasReferencedResolversUnionTypes = false;
571+
protected _hasReferencedResolversUnionParentTypes = false;
571572
protected _resolversUnionTypes: Record<string, string> = {};
573+
protected _resolversUnionParentTypes: Record<string, string> = {};
572574
protected _rootTypeNames = new Set<string>();
573575
protected _globalDeclarations = new Set<string>();
574576
protected _federation: ApolloFederation;
@@ -625,18 +627,26 @@ export class BaseResolversVisitor<
625627
this.convertName,
626628
this.config.namespacedImportName
627629
);
628-
this._resolversTypes = this.createResolversFields(
629-
type => this.applyResolverTypeWrapper(type),
630-
type => this.clearResolverTypeWrapper(type),
631-
name => this.getTypeToUse(name)
632-
);
633-
this._resolversParentTypes = this.createResolversFields(
634-
type => type,
635-
type => type,
636-
name => this.getParentTypeToUse(name),
637-
namedType => !isEnumType(namedType)
638-
);
639-
this._resolversUnionTypes = this.createResolversUnionTypes();
630+
631+
this._resolversTypes = this.createResolversFields({
632+
applyWrapper: type => this.applyResolverTypeWrapper(type),
633+
clearWrapper: type => this.clearResolverTypeWrapper(type),
634+
getTypeToUse: name => this.getTypeToUse(name),
635+
referencedUnionType: 'ResolversUnionTypes',
636+
});
637+
this._resolversParentTypes = this.createResolversFields({
638+
applyWrapper: type => type,
639+
clearWrapper: type => type,
640+
getTypeToUse: name => this.getParentTypeToUse(name),
641+
referencedUnionType: 'ResolversUnionParentTypes',
642+
shouldInclude: namedType => !isEnumType(namedType),
643+
});
644+
this._resolversUnionTypes = this.createResolversUnionTypes({
645+
getTypeToUse: this.getTypeToUse,
646+
});
647+
this._resolversUnionParentTypes = this.createResolversUnionTypes({
648+
getTypeToUse: this.getParentTypeToUse,
649+
});
640650
this._fieldContextTypeMap = this.createFieldContextTypeMap();
641651
this._directiveContextTypesMap = this.createDirectivedContextType();
642652
this._directiveResolverMappings = rawConfig.directiveResolverMappings ?? {};
@@ -700,12 +710,19 @@ export class BaseResolversVisitor<
700710
}
701711

702712
// Kamil: this one is heeeeavvyyyy
703-
protected createResolversFields(
704-
applyWrapper: (str: string) => string,
705-
clearWrapper: (str: string) => string,
706-
getTypeToUse: (str: string) => string,
707-
shouldInclude?: (type: GraphQLNamedType) => boolean
708-
): ResolverTypes {
713+
protected createResolversFields({
714+
applyWrapper,
715+
clearWrapper,
716+
getTypeToUse,
717+
referencedUnionType,
718+
shouldInclude,
719+
}: {
720+
applyWrapper: (str: string) => string;
721+
clearWrapper: (str: string) => string;
722+
getTypeToUse: (str: string) => string;
723+
referencedUnionType: 'ResolversUnionTypes' | 'ResolversUnionParentTypes';
724+
shouldInclude?: (type: GraphQLNamedType) => boolean;
725+
}): ResolverTypes {
709726
const allSchemaTypes = this._schema.getTypeMap();
710727
const typeNames = this._federation.filterTypeNames(Object.keys(allSchemaTypes));
711728

@@ -766,8 +783,12 @@ export class BaseResolversVisitor<
766783
} else if (isScalar) {
767784
prev[typeName] = applyWrapper(this._getScalar(typeName));
768785
} else if (isUnionType(schemaType)) {
769-
this._hasReferencedResolversUnionTypes = true;
770-
const resolversType = this.convertName('ResolversUnionTypes');
786+
if (referencedUnionType === 'ResolversUnionTypes') {
787+
this._hasReferencedResolversUnionTypes = true;
788+
} else if (referencedUnionType === 'ResolversUnionParentTypes') {
789+
this._hasReferencedResolversUnionParentTypes = true;
790+
}
791+
const resolversType = this.convertName(referencedUnionType);
771792
prev[typeName] = applyWrapper(`${resolversType}['${typeName}']`);
772793
} else if (isEnumType(schemaType)) {
773794
prev[typeName] = this.convertName(typeName, { useTypesPrefix: this.config.enumPrefix }, true);
@@ -861,7 +882,11 @@ export class BaseResolversVisitor<
861882
return `Array<${t}>`;
862883
}
863884

864-
protected createResolversUnionTypes(): Record<string, string> {
885+
protected createResolversUnionTypes({
886+
getTypeToUse,
887+
}: {
888+
getTypeToUse: (name: string) => string;
889+
}): Record<string, string> {
865890
if (!this._hasReferencedResolversUnionTypes) {
866891
return {};
867892
}
@@ -887,11 +912,8 @@ export class BaseResolversVisitor<
887912

888913
// 2b. Find fields to Omit if needed.
889914
// - If no field to Omit, "type with maybe Omit" is typescript type i.e. no Omit
890-
// - If there are fields to Omit, "type with maybe Omit"
891-
const fieldsToOmit = this.getRelevantFieldsToOmit({
892-
schemaType: unionMemberType,
893-
getTypeToUse: this.getTypeToUse,
894-
});
915+
// - If there are fields to Omit, keep track of these "type with maybe Omit" to replace in original unionMemberValue
916+
const fieldsToOmit = this.getRelevantFieldsToOmit({ schemaType: unionMemberType, getTypeToUse });
895917
if (fieldsToOmit.length > 0) {
896918
unionMemberValue = this.replaceFieldsInType(unionMemberValue, fieldsToOmit);
897919
}
@@ -996,6 +1018,24 @@ export class BaseResolversVisitor<
9961018
).string;
9971019
}
9981020

1021+
public buildResolversUnionParentTypes(): string {
1022+
if (Object.keys(this._resolversUnionParentTypes).length === 0) {
1023+
return '';
1024+
}
1025+
1026+
const declarationKind = 'type';
1027+
return new DeclarationBlock(this._declarationBlockConfig)
1028+
.export()
1029+
.asKind(declarationKind)
1030+
.withName(this.convertName('ResolversUnionParentTypes'))
1031+
.withComment('Mapping of union parent types')
1032+
.withBlock(
1033+
Object.entries(this._resolversUnionParentTypes)
1034+
.map(([typeName, value]) => indent(`${typeName}: ${value}${this.getPunctuation(declarationKind)}`))
1035+
.join('\n')
1036+
).string;
1037+
}
1038+
9991039
public get schema(): GraphQLSchema {
10001040
return this._schema;
10011041
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs
246246
const resolversTypeMapping = visitor.buildResolversTypes();
247247
const resolversParentTypeMapping = visitor.buildResolversParentTypes();
248248
const resolversUnionTypesMapping = visitor.buildResolversUnionTypes();
249+
const resolversUnionParentTypesMapping = visitor.buildResolversUnionParentTypes();
249250
const { getRootResolver, getAllDirectiveResolvers, mappersImports, unusedMappers, hasScalars } = visitor;
250251

251252
if (hasScalars()) {
@@ -284,6 +285,7 @@ export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs
284285
content: [
285286
header,
286287
resolversUnionTypesMapping,
288+
resolversUnionParentTypesMapping,
287289
resolversTypeMapping,
288290
resolversParentTypeMapping,
289291
...visitorResult.definitions.filter(d => typeof d === 'string'),

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

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ export type ResolversUnionTypes = ResolversObject<{
140140
MyUnion: ( Omit<MyType, 'unionChild'> & { unionChild?: Maybe<ResolversTypes['ChildUnion']> } ) | ( MyOtherType );
141141
}>;
142142
143+
/** Mapping of union parent types */
144+
export type ResolversUnionParentTypes = ResolversObject<{
145+
ChildUnion: ( Child ) | ( MyOtherType );
146+
MyUnion: ( Omit<MyType, 'unionChild'> & { unionChild?: Maybe<ResolversParentTypes['ChildUnion']> } ) | ( MyOtherType );
147+
}>;
148+
143149
/** Mapping between all available schema types and the resolvers types */
144150
export type ResolversTypes = ResolversObject<{
145151
MyType: ResolverTypeWrapper<Omit<MyType, 'unionChild'> & { unionChild?: Maybe<ResolversTypes['ChildUnion']> }>;
@@ -164,13 +170,13 @@ export type ResolversParentTypes = ResolversObject<{
164170
String: Scalars['String'];
165171
Child: Child;
166172
MyOtherType: MyOtherType;
167-
ChildUnion: ResolversUnionTypes['ChildUnion'];
173+
ChildUnion: ResolversUnionParentTypes['ChildUnion'];
168174
Query: {};
169175
Subscription: {};
170176
Node: ResolversParentTypes['SomeNode'];
171177
ID: Scalars['ID'];
172178
SomeNode: SomeNode;
173-
MyUnion: ResolversUnionTypes['MyUnion'];
179+
MyUnion: ResolversUnionParentTypes['MyUnion'];
174180
MyScalar: Scalars['MyScalar'];
175181
Int: Scalars['Int'];
176182
Boolean: Scalars['Boolean'];
@@ -343,6 +349,12 @@ export type ResolversUnionTypes = ResolversObject<{
343349
MyUnion: ( Omit<Types.MyType, 'unionChild'> & { unionChild?: Types.Maybe<ResolversTypes['ChildUnion']> } ) | ( Types.MyOtherType );
344350
}>;
345351
352+
/** Mapping of union parent types */
353+
export type ResolversUnionParentTypes = ResolversObject<{
354+
ChildUnion: ( Types.Child ) | ( Types.MyOtherType );
355+
MyUnion: ( Omit<Types.MyType, 'unionChild'> & { unionChild?: Types.Maybe<ResolversParentTypes['ChildUnion']> } ) | ( Types.MyOtherType );
356+
}>;
357+
346358
/** Mapping between all available schema types and the resolvers types */
347359
export type ResolversTypes = ResolversObject<{
348360
MyType: ResolverTypeWrapper<Omit<Types.MyType, 'unionChild'> & { unionChild?: Types.Maybe<ResolversTypes['ChildUnion']> }>;
@@ -367,13 +379,13 @@ export type ResolversParentTypes = ResolversObject<{
367379
String: Types.Scalars['String'];
368380
Child: Types.Child;
369381
MyOtherType: Types.MyOtherType;
370-
ChildUnion: ResolversUnionTypes['ChildUnion'];
382+
ChildUnion: ResolversUnionParentTypes['ChildUnion'];
371383
Query: {};
372384
Subscription: {};
373385
Node: ResolversParentTypes['SomeNode'];
374386
ID: Types.Scalars['ID'];
375387
SomeNode: Types.SomeNode;
376-
MyUnion: ResolversUnionTypes['MyUnion'];
388+
MyUnion: ResolversUnionParentTypes['MyUnion'];
377389
MyScalar: Types.Scalars['MyScalar'];
378390
Int: Types.Scalars['Int'];
379391
Boolean: Types.Scalars['Boolean'];
@@ -600,6 +612,12 @@ export type ResolversUnionTypes = ResolversObject<{
600612
MyUnion: ( Omit<MyType, 'unionChild'> & { unionChild?: Maybe<ResolversTypes['ChildUnion']> } ) | ( MyOtherType );
601613
}>;
602614
615+
/** Mapping of union parent types */
616+
export type ResolversUnionParentTypes = ResolversObject<{
617+
ChildUnion: ( Child ) | ( MyOtherType );
618+
MyUnion: ( Omit<MyType, 'unionChild'> & { unionChild?: Maybe<ResolversParentTypes['ChildUnion']> } ) | ( MyOtherType );
619+
}>;
620+
603621
/** Mapping between all available schema types and the resolvers types */
604622
export type ResolversTypes = ResolversObject<{
605623
MyType: ResolverTypeWrapper<Omit<MyType, 'unionChild'> & { unionChild?: Maybe<ResolversTypes['ChildUnion']> }>;
@@ -624,13 +642,13 @@ export type ResolversParentTypes = ResolversObject<{
624642
String: Scalars['String'];
625643
Child: Child;
626644
MyOtherType: MyOtherType;
627-
ChildUnion: ResolversUnionTypes['ChildUnion'];
645+
ChildUnion: ResolversUnionParentTypes['ChildUnion'];
628646
Query: {};
629647
Subscription: {};
630648
Node: ResolversParentTypes['SomeNode'];
631649
ID: Scalars['ID'];
632650
SomeNode: SomeNode;
633-
MyUnion: ResolversUnionTypes['MyUnion'];
651+
MyUnion: ResolversUnionParentTypes['MyUnion'];
634652
MyScalar: Scalars['MyScalar'];
635653
Int: Scalars['Int'];
636654
Boolean: Scalars['Boolean'];

0 commit comments

Comments
 (0)