Skip to content

Commit

Permalink
[typescript-resolvers] Fix resolvers union types used in `ResolversPa…
Browse files Browse the repository at this point in the history
…rentTypes` (#9206)
  • Loading branch information
eddeee888 authored Mar 21, 2023
1 parent 0f53581 commit e567901
Show file tree
Hide file tree
Showing 7 changed files with 348 additions and 66 deletions.
63 changes: 63 additions & 0 deletions .changeset/blue-pans-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
'@graphql-codegen/visitor-plugin-common': patch
'@graphql-codegen/typescript-resolvers': patch
---

Fix `ResolversUnionTypes` being used in `ResolversParentTypes`

Previously, objects with mappable fields are converted to Omit format that references its own type group or `ResolversTypes` or `ResolversParentTypes` e.g.

```ts
export type ResolversTypes = {
Book: ResolverTypeWrapper<BookMapper>;
BookPayload: ResolversTypes["BookResult"] | ResolversTypes["StandardError"];
// Note: `result` on the next line references `ResolversTypes["Book"]`
BookResult: ResolverTypeWrapper<Omit<BookResult, "result"> & { result?: Maybe<ResolversTypes["Book"]> }>;
StandardError: ResolverTypeWrapper<StandardError>;
};

export type ResolversParentTypes = {
Book: BookMapper;
BookPayload: ResolversParentTypes["BookResult"] | ResolversParentTypes["StandardError"];
// Note: `result` on the next line references `ResolversParentTypes["Book"]`
BookResult: Omit<BookResult, "result"> & { result?: Maybe<ResolversParentTypes["Book"]> };
StandardError: StandardError;
};
```

In https://github.com/dotansimha/graphql-code-generator/pull/9069, we extracted resolver union types to its own group:

```ts
export type ResolversUnionTypes = {
// Note: `result` on the next line references `ResolversTypes["Book"]` which is only correct for the `ResolversTypes` case
BookPayload: (Omit<BookResult, "result"> & { result?: Maybe<ResolversTypes["Book"]> }) | StandardError;
};

export type ResolversTypes = {
Book: ResolverTypeWrapper<BookMapper>;
BookPayload: ResolverTypeWrapper<ResolversUnionTypes["BookPayload"]>;
BookResult: ResolverTypeWrapper<Omit<BookResult, "result"> & { result?: Maybe<ResolversTypes["Book"]> }>;
StandardError: ResolverTypeWrapper<StandardError>;
};

export type ResolversParentTypes = {
Book: BookMapper;
BookPayload: ResolversUnionTypes["BookPayload"];
BookResult: Omit<BookResult, "result"> & { result?: Maybe<ResolversParentTypes["Book"]> };
StandardError: StandardError;
};
```

This change creates an extra `ResolversUnionParentTypes` that is referenced by `ResolversParentTypes` to ensure backwards compatibility:

```ts
export type ResolversUnionTypes = {
BookPayload: (Omit<BookResult, "result"> & { result?: Maybe<ResolversParentTypes["Book"]> }) | StandardError;
};

// ... and the reference is changed in ResolversParentTypes:
export type ResolversParentTypes = {
// ... other fields
BookPayload: ResolversUnionParentTypes["BookPayload"];
};
```
7 changes: 6 additions & 1 deletion dev-test/modules/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@ export type ResolversUnionTypes = {
PaymentOption: CreditCard | Paypal;
};

/** Mapping of union parent types */
export type ResolversUnionParentTypes = {
PaymentOption: CreditCard | Paypal;
};

/** Mapping between all available schema types and the resolvers types */
export type ResolversTypes = {
Article: ResolverTypeWrapper<Article>;
Expand Down Expand Up @@ -198,7 +203,7 @@ export type ResolversParentTypes = {
ID: Scalars['ID'];
Int: Scalars['Int'];
Mutation: {};
PaymentOption: ResolversUnionTypes['PaymentOption'];
PaymentOption: ResolversUnionParentTypes['PaymentOption'];
Paypal: Paypal;
Query: {};
String: Scalars['String'];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,9 @@ export class BaseResolversVisitor<
protected _resolversTypes: ResolverTypes = {};
protected _resolversParentTypes: ResolverParentTypes = {};
protected _hasReferencedResolversUnionTypes = false;
protected _hasReferencedResolversUnionParentTypes = false;
protected _resolversUnionTypes: Record<string, string> = {};
protected _resolversUnionParentTypes: Record<string, string> = {};
protected _rootTypeNames = new Set<string>();
protected _globalDeclarations = new Set<string>();
protected _federation: ApolloFederation;
Expand Down Expand Up @@ -625,18 +627,26 @@ export class BaseResolversVisitor<
this.convertName,
this.config.namespacedImportName
);
this._resolversTypes = this.createResolversFields(
type => this.applyResolverTypeWrapper(type),
type => this.clearResolverTypeWrapper(type),
name => this.getTypeToUse(name)
);
this._resolversParentTypes = this.createResolversFields(
type => type,
type => type,
name => this.getParentTypeToUse(name),
namedType => !isEnumType(namedType)
);
this._resolversUnionTypes = this.createResolversUnionTypes();

this._resolversTypes = this.createResolversFields({
applyWrapper: type => this.applyResolverTypeWrapper(type),
clearWrapper: type => this.clearResolverTypeWrapper(type),
getTypeToUse: name => this.getTypeToUse(name),
referencedUnionType: 'ResolversUnionTypes',
});
this._resolversParentTypes = this.createResolversFields({
applyWrapper: type => type,
clearWrapper: type => type,
getTypeToUse: name => this.getParentTypeToUse(name),
referencedUnionType: 'ResolversUnionParentTypes',
shouldInclude: namedType => !isEnumType(namedType),
});
this._resolversUnionTypes = this.createResolversUnionTypes({
getTypeToUse: this.getTypeToUse,
});
this._resolversUnionParentTypes = this.createResolversUnionTypes({
getTypeToUse: this.getParentTypeToUse,
});
this._fieldContextTypeMap = this.createFieldContextTypeMap();
this._directiveContextTypesMap = this.createDirectivedContextType();
this._directiveResolverMappings = rawConfig.directiveResolverMappings ?? {};
Expand Down Expand Up @@ -700,12 +710,19 @@ export class BaseResolversVisitor<
}

// Kamil: this one is heeeeavvyyyy
protected createResolversFields(
applyWrapper: (str: string) => string,
clearWrapper: (str: string) => string,
getTypeToUse: (str: string) => string,
shouldInclude?: (type: GraphQLNamedType) => boolean
): ResolverTypes {
protected createResolversFields({
applyWrapper,
clearWrapper,
getTypeToUse,
referencedUnionType,
shouldInclude,
}: {
applyWrapper: (str: string) => string;
clearWrapper: (str: string) => string;
getTypeToUse: (str: string) => string;
referencedUnionType: 'ResolversUnionTypes' | 'ResolversUnionParentTypes';
shouldInclude?: (type: GraphQLNamedType) => boolean;
}): ResolverTypes {
const allSchemaTypes = this._schema.getTypeMap();
const typeNames = this._federation.filterTypeNames(Object.keys(allSchemaTypes));

Expand Down Expand Up @@ -766,8 +783,12 @@ export class BaseResolversVisitor<
} else if (isScalar) {
prev[typeName] = applyWrapper(this._getScalar(typeName));
} else if (isUnionType(schemaType)) {
this._hasReferencedResolversUnionTypes = true;
const resolversType = this.convertName('ResolversUnionTypes');
if (referencedUnionType === 'ResolversUnionTypes') {
this._hasReferencedResolversUnionTypes = true;
} else if (referencedUnionType === 'ResolversUnionParentTypes') {
this._hasReferencedResolversUnionParentTypes = true;
}
const resolversType = this.convertName(referencedUnionType);
prev[typeName] = applyWrapper(`${resolversType}['${typeName}']`);
} else if (isEnumType(schemaType)) {
prev[typeName] = this.convertName(typeName, { useTypesPrefix: this.config.enumPrefix }, true);
Expand Down Expand Up @@ -861,7 +882,11 @@ export class BaseResolversVisitor<
return `Array<${t}>`;
}

protected createResolversUnionTypes(): Record<string, string> {
protected createResolversUnionTypes({
getTypeToUse,
}: {
getTypeToUse: (name: string) => string;
}): Record<string, string> {
if (!this._hasReferencedResolversUnionTypes) {
return {};
}
Expand All @@ -887,11 +912,8 @@ export class BaseResolversVisitor<

// 2b. Find fields to Omit if needed.
// - If no field to Omit, "type with maybe Omit" is typescript type i.e. no Omit
// - If there are fields to Omit, "type with maybe Omit"
const fieldsToOmit = this.getRelevantFieldsToOmit({
schemaType: unionMemberType,
getTypeToUse: this.getTypeToUse,
});
// - If there are fields to Omit, keep track of these "type with maybe Omit" to replace in original unionMemberValue
const fieldsToOmit = this.getRelevantFieldsToOmit({ schemaType: unionMemberType, getTypeToUse });
if (fieldsToOmit.length > 0) {
unionMemberValue = this.replaceFieldsInType(unionMemberValue, fieldsToOmit);
}
Expand Down Expand Up @@ -996,6 +1018,24 @@ export class BaseResolversVisitor<
).string;
}

public buildResolversUnionParentTypes(): string {
if (Object.keys(this._resolversUnionParentTypes).length === 0) {
return '';
}

const declarationKind = 'type';
return new DeclarationBlock(this._declarationBlockConfig)
.export()
.asKind(declarationKind)
.withName(this.convertName('ResolversUnionParentTypes'))
.withComment('Mapping of union parent types')
.withBlock(
Object.entries(this._resolversUnionParentTypes)
.map(([typeName, value]) => indent(`${typeName}: ${value}${this.getPunctuation(declarationKind)}`))
.join('\n')
).string;
}

public get schema(): GraphQLSchema {
return this._schema;
}
Expand Down
2 changes: 2 additions & 0 deletions packages/plugins/typescript/resolvers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs
const resolversTypeMapping = visitor.buildResolversTypes();
const resolversParentTypeMapping = visitor.buildResolversParentTypes();
const resolversUnionTypesMapping = visitor.buildResolversUnionTypes();
const resolversUnionParentTypesMapping = visitor.buildResolversUnionParentTypes();
const { getRootResolver, getAllDirectiveResolvers, mappersImports, unusedMappers, hasScalars } = visitor;

if (hasScalars()) {
Expand Down Expand Up @@ -284,6 +285,7 @@ export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs
content: [
header,
resolversUnionTypesMapping,
resolversUnionParentTypesMapping,
resolversTypeMapping,
resolversParentTypeMapping,
...visitorResult.definitions.filter(d => typeof d === 'string'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,12 @@ export type ResolversUnionTypes = ResolversObject<{
MyUnion: ( Omit<MyType, 'unionChild'> & { unionChild?: Maybe<ResolversTypes['ChildUnion']> } ) | ( MyOtherType );
}>;
/** Mapping of union parent types */
export type ResolversUnionParentTypes = ResolversObject<{
ChildUnion: ( Child ) | ( MyOtherType );
MyUnion: ( Omit<MyType, 'unionChild'> & { unionChild?: Maybe<ResolversParentTypes['ChildUnion']> } ) | ( MyOtherType );
}>;
/** Mapping between all available schema types and the resolvers types */
export type ResolversTypes = ResolversObject<{
MyType: ResolverTypeWrapper<Omit<MyType, 'unionChild'> & { unionChild?: Maybe<ResolversTypes['ChildUnion']> }>;
Expand All @@ -164,13 +170,13 @@ export type ResolversParentTypes = ResolversObject<{
String: Scalars['String'];
Child: Child;
MyOtherType: MyOtherType;
ChildUnion: ResolversUnionTypes['ChildUnion'];
ChildUnion: ResolversUnionParentTypes['ChildUnion'];
Query: {};
Subscription: {};
Node: ResolversParentTypes['SomeNode'];
ID: Scalars['ID'];
SomeNode: SomeNode;
MyUnion: ResolversUnionTypes['MyUnion'];
MyUnion: ResolversUnionParentTypes['MyUnion'];
MyScalar: Scalars['MyScalar'];
Int: Scalars['Int'];
Boolean: Scalars['Boolean'];
Expand Down Expand Up @@ -343,6 +349,12 @@ export type ResolversUnionTypes = ResolversObject<{
MyUnion: ( Omit<Types.MyType, 'unionChild'> & { unionChild?: Types.Maybe<ResolversTypes['ChildUnion']> } ) | ( Types.MyOtherType );
}>;
/** Mapping of union parent types */
export type ResolversUnionParentTypes = ResolversObject<{
ChildUnion: ( Types.Child ) | ( Types.MyOtherType );
MyUnion: ( Omit<Types.MyType, 'unionChild'> & { unionChild?: Types.Maybe<ResolversParentTypes['ChildUnion']> } ) | ( Types.MyOtherType );
}>;
/** Mapping between all available schema types and the resolvers types */
export type ResolversTypes = ResolversObject<{
MyType: ResolverTypeWrapper<Omit<Types.MyType, 'unionChild'> & { unionChild?: Types.Maybe<ResolversTypes['ChildUnion']> }>;
Expand All @@ -367,13 +379,13 @@ export type ResolversParentTypes = ResolversObject<{
String: Types.Scalars['String'];
Child: Types.Child;
MyOtherType: Types.MyOtherType;
ChildUnion: ResolversUnionTypes['ChildUnion'];
ChildUnion: ResolversUnionParentTypes['ChildUnion'];
Query: {};
Subscription: {};
Node: ResolversParentTypes['SomeNode'];
ID: Types.Scalars['ID'];
SomeNode: Types.SomeNode;
MyUnion: ResolversUnionTypes['MyUnion'];
MyUnion: ResolversUnionParentTypes['MyUnion'];
MyScalar: Types.Scalars['MyScalar'];
Int: Types.Scalars['Int'];
Boolean: Types.Scalars['Boolean'];
Expand Down Expand Up @@ -600,6 +612,12 @@ export type ResolversUnionTypes = ResolversObject<{
MyUnion: ( Omit<MyType, 'unionChild'> & { unionChild?: Maybe<ResolversTypes['ChildUnion']> } ) | ( MyOtherType );
}>;
/** Mapping of union parent types */
export type ResolversUnionParentTypes = ResolversObject<{
ChildUnion: ( Child ) | ( MyOtherType );
MyUnion: ( Omit<MyType, 'unionChild'> & { unionChild?: Maybe<ResolversParentTypes['ChildUnion']> } ) | ( MyOtherType );
}>;
/** Mapping between all available schema types and the resolvers types */
export type ResolversTypes = ResolversObject<{
MyType: ResolverTypeWrapper<Omit<MyType, 'unionChild'> & { unionChild?: Maybe<ResolversTypes['ChildUnion']> }>;
Expand All @@ -624,13 +642,13 @@ export type ResolversParentTypes = ResolversObject<{
String: Scalars['String'];
Child: Child;
MyOtherType: MyOtherType;
ChildUnion: ResolversUnionTypes['ChildUnion'];
ChildUnion: ResolversUnionParentTypes['ChildUnion'];
Query: {};
Subscription: {};
Node: ResolversParentTypes['SomeNode'];
ID: Scalars['ID'];
SomeNode: SomeNode;
MyUnion: ResolversUnionTypes['MyUnion'];
MyUnion: ResolversUnionParentTypes['MyUnion'];
MyScalar: Scalars['MyScalar'];
Int: Scalars['Int'];
Boolean: Scalars['Boolean'];
Expand Down
Loading

0 comments on commit e567901

Please sign in to comment.