Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[typescript-resolvers] Extract interface types into ResolversInterfaceTypes + add interface to resolversNonOptionalTypename + simplify ResolversUnionTypes #9229

Merged
merged 2 commits into from
Apr 27, 2023

Conversation

eddeee888
Copy link
Collaborator

@eddeee888 eddeee888 commented Mar 26, 2023

Description

1. ResolversInterfaceTypes is a new type that keeps track of a GraphQL interface and its implementing types

For example, consider this schema:

extend type Query {
  character(id: ID!): CharacterNode
}
interface CharacterNode {
  id: ID!
}
type Wizard implements CharacterNode {
  id: ID!
  screenName: String!
  spells: [String!]!
}
type Fighter implements CharacterNode {
  id: ID!
  screenName: String!
  powerLevel: Int!
}

The generated types will look like this:

export type ResolversInterfaceTypes<RefType extends Record<string, unknown>> = {
  CharacterNode: Fighter | Wizard;
};
export type ResolversTypes = {
  // other types...
  CharacterNode: ResolverTypeWrapper<ResolversInterfaceTypes<ResolversTypes>["CharacterNode"]>;
  Fighter: ResolverTypeWrapper<Fighter>;
  Wizard: ResolverTypeWrapper<Wizard>;
  // other types...
};
export type ResolversParentTypes = {
  // other types...
  CharacterNode: ResolversInterfaceTypes<ResolversParentTypes>["CharacterNode"];
  Fighter: Fighter;
  Wizard: Wizard;
  // other types...
};

The RefType generic is used to reference back to ResolversTypes and ResolversParentTypes in some cases such as field returning a Union.

2. resolversNonOptionalTypename affects ResolversInterfaceTypes

Using the schema above, if we use resolversNonOptionalTypename option:

const config: CodegenConfig = {
  schema: 'src/schema/**/*.graphql',
  generates: {
    'src/schema/types.ts': {
      plugins: ['typescript', 'typescript-resolvers'],
      config: {
        resolversNonOptionalTypename: true // Or `resolversNonOptionalTypename: { interfaceImplementingType: true }`
      }
    },
  },
};

Then, the generated type looks like this:

export type ResolversInterfaceTypes<RefType extends Record<string, unknown>> = {
  CharacterNode: (Fighter & { __typename: "Fighter" }) | (Wizard & { __typename: "Wizard" });
};
export type ResolversTypes = {
  // other types...
  CharacterNode: ResolverTypeWrapper<ResolversInterfaceTypes<ResolversTypes>["CharacterNode"]>;
  Fighter: ResolverTypeWrapper<Fighter>;
  Wizard: ResolverTypeWrapper<Wizard>;
  // other types...
};
export type ResolversParentTypes = {
  // other types...
  CharacterNode: ResolversInterfaceTypes<ResolversParentTypes>["CharacterNode"];
  Fighter: Fighter;
  Wizard: Wizard;
  // other types...
};

3. Simplify ResolversUnionTypes and ResolversUnionParentTypes with generic

Previously, we create ResolversUnionTypes and ResolversUnionParentTypes since each nested union member field may refer back to ResolversTypes and ResolversParentTypes respectively.

This PR simplifies it by using a generic (similar to ResolversInterfaceTypes)

Type of change

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

How Has This Been Tested?

TODO

Test Environment:

  • OS: MacOS
  • NodeJS: 18

Checklist:

  • I have followed the CONTRIBUTING doc and the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Further comments

If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc...

@changeset-bot
Copy link

changeset-bot bot commented Mar 26, 2023

🦋 Changeset detected

Latest commit: 9a5572c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
@graphql-codegen/visitor-plugin-common Minor
@graphql-codegen/typescript-resolvers Minor
@graphql-codegen/typescript-document-nodes Patch
@graphql-codegen/gql-tag-operations Patch
@graphql-codegen/typescript-operations Patch
@graphql-codegen/typed-document-node Patch
@graphql-codegen/typescript Patch
@graphql-codegen/graphql-modules-preset Patch
@graphql-codegen/client-preset Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Contributor

github-actions bot commented Mar 26, 2023

🚀 Snapshot Release (alpha)

The latest changes of this PR are available as alpha on npm (based on the declared changesets):

Package Version Info
@graphql-codegen/visitor-plugin-common 3.2.0-alpha-20230423100039-9a5572c84 npm ↗︎ unpkg ↗︎
@graphql-codegen/typescript-document-nodes 3.0.5-alpha-20230423100039-9a5572c84 npm ↗︎ unpkg ↗︎
@graphql-codegen/gql-tag-operations 3.0.2-alpha-20230423100039-9a5572c84 npm ↗︎ unpkg ↗︎
@graphql-codegen/typescript-operations 3.0.5-alpha-20230423100039-9a5572c84 npm ↗︎ unpkg ↗︎
@graphql-codegen/typescript-resolvers 3.3.0-alpha-20230423100039-9a5572c84 npm ↗︎ unpkg ↗︎
@graphql-codegen/typed-document-node 4.0.2-alpha-20230423100039-9a5572c84 npm ↗︎ unpkg ↗︎
@graphql-codegen/typescript 3.0.5-alpha-20230423100039-9a5572c84 npm ↗︎ unpkg ↗︎
@graphql-codegen/client-preset 3.0.2-alpha-20230423100039-9a5572c84 npm ↗︎ unpkg ↗︎
@graphql-codegen/graphql-modules-preset 3.1.4-alpha-20230423100039-9a5572c84 npm ↗︎ unpkg ↗︎

@github-actions
Copy link
Contributor

github-actions bot commented Mar 26, 2023

💻 Website Preview

The latest changes are available as preview in: https://1aadfaa1.graphql-code-generator.pages.dev

@eddeee888 eddeee888 force-pushed the typescript-resolvers-extract-interface-types branch 2 times, most recently from b418594 to 8f0a018 Compare March 29, 2023 09:34
@@ -632,13 +634,15 @@ export class BaseResolversVisitor<
applyWrapper: type => this.applyResolverTypeWrapper(type),
clearWrapper: type => this.clearResolverTypeWrapper(type),
getTypeToUse: name => this.getTypeToUse(name),
currentType: 'ResolversTypes',
Copy link
Collaborator Author

@eddeee888 eddeee888 Mar 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are using currentType as a generic so we don't have to create two types like ResolversUnionTypes and ResolversUnionParentTypes

Comment on lines +774 to +769
} else if (isInterfaceType(schemaType)) {
this._hasReferencedResolversInterfaceTypes = true;
const type = this.convertName('ResolversInterfaceTypes');
const generic = this.convertName(currentType);
prev[typeName] = applyWrapper(`${type}<${generic}>['${typeName}']`);
return prev;
Copy link
Collaborator Author

@eddeee888 eddeee888 Mar 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Interface block must be below hasDefaultMapper && !hasPlaceholder(this.config.defaultMapper.type) if block.

Otherwise, the default mapper case with no placeholder e.g. config.defaultMapper: any will incorrectly generate ResolversInterfaceTypes

return new DeclarationBlock(this._declarationBlockConfig)
.export()
.asKind(declarationKind)
.withName(this.convertName('ResolversInterfaceTypes'), `<RefType extends Record<string, unknown>>`)
Copy link
Collaborator Author

@eddeee888 eddeee888 Mar 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RefType generic will be ResolversTypes and ResolversParentTypes in the final generated type. This will be used to refer back to the original types in cases
e.g. omitting the original field because it's a union

Comment on lines +1004 to +948
AnotherNode: ResolverTypeWrapper<any>;
WithChild: ResolverTypeWrapper<any>;
WithChildren: ResolverTypeWrapper<any>;
Copy link
Collaborator Author

@eddeee888 eddeee888 Apr 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case of default mapper without placeholder, we can treat interface implementing types the same as how union treats it: typing it as ResolverTypeWrapper<DefaultMapper> instead of ResolversTypes['typeA'] | ResolversTypes['typeB']

The reason being...

ResolversTypes['typeA'] | ResolversTypes['typeB']

resolves to...

ResolverTypeWrapper<DefaultMapper> | ResolverTypeWrapper<DefaultMapper>

so we can simplify it to just ResolverTypeWrapper<DefaultMapper>

@eddeee888 eddeee888 marked this pull request as ready for review April 3, 2023 13:30
@saihaj saihaj requested review from dotansimha and gilgardosh April 3, 2023 18:26
@eddeee888 eddeee888 force-pushed the typescript-resolvers-extract-interface-types branch from 553afcd to 4d2d9b6 Compare April 5, 2023 09:28
@eddeee888 eddeee888 force-pushed the typescript-resolvers-extract-interface-types branch 3 times, most recently from 3cf04c7 to 5d3298f Compare April 23, 2023 09:40
@eddeee888 eddeee888 force-pushed the typescript-resolvers-extract-interface-types branch from 5d3298f to 9a5572c Compare April 23, 2023 09:59
Copy link
Owner

@dotansimha dotansimha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Let's also wait for @saihaj to review

@dotansimha dotansimha requested a review from saihaj April 23, 2023 10:03
@eddeee888 eddeee888 changed the title [typescript-resolvers] Extract interface types into own type + add interface to resolversNonOptionalTypename [typescript-resolvers] Extract interface types into ResolversInterfaceTypes + add interface to resolversNonOptionalTypename + simplify ResolverUnionTypes Apr 23, 2023
@eddeee888 eddeee888 changed the title [typescript-resolvers] Extract interface types into ResolversInterfaceTypes + add interface to resolversNonOptionalTypename + simplify ResolverUnionTypes [typescript-resolvers] Extract interface types into ResolversInterfaceTypes + add interface to resolversNonOptionalTypename + simplify ResolversUnionTypes Apr 23, 2023
@saihaj
Copy link
Collaborator

saihaj commented Apr 27, 2023

sorry for the delay @eddeee888 🥲

@saihaj saihaj merged commit 5aa95aa into master Apr 27, 2023
@saihaj saihaj deleted the typescript-resolvers-extract-interface-types branch April 27, 2023 22:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants