eddeee888 / graphql-code-generator-plugins

List of GraphQL Code Generator plugins that complements the official plugins.
MIT License
53 stars 13 forks source link

[BUG] Mappers are not applied to Interfaces #278

Open eddeee888 opened 6 months ago

eddeee888 commented 6 months ago

From https://github.com/eddeee888/graphql-code-generator-plugins/issues/266#issuecomment-2111635370

Thanks for the fix above! I created another repo here for a related bug where the mapper for the interface type itself isn't being set properly as the Resolver return type.

The way I was anticipating it would work is that (based on the example in the linked repo):

  1. The user query returns a User interface type. It should expect the return value of the UserMapper
  2. The __resolveType on User.ts should have the UserMapper as the parent type, and can be used to dictate which of the implementing types is being returned
  3. The parent for AdminUser type resolver functions should be the AdminMapper (and similar for CustomerMapper). If they are not defined then it should default to UserMapper as the parent type. Please let me know if I'm thinking about this the wrong way! The reason I would like these types is that I have a base table in my DB that maps cleanly to the interface type, but we need to fetch additional data to resolve fields on the actual object type.
eddeee888 commented 6 months ago

Hi @nikhilgupta345,

Let's track your issue here.

I believe your use case is correct in terms of how GraphQL works. It's the type generation that's the issue.

There are 2 main problems here:

1. Server Preset is not picking up Interface mappers, therefore UserMapper is not applied correctly

To wire up UserMapper manually, we can do this:

schema: "**/*.graphql"
generates:
  src/schema:
    preset: "@eddeee888/gcg-typescript-resolver-files"
    watchPattern: "**/*.mappers.ts"
    presetConfig:
      mode: "merged"
      resolverGeneration: all
      typesPluginsConfig:
        resolversNonOptionalTypename: false
        mappers:
          User: "./schema.mappers#UserMapper" # manually wire up UserMapper to User

However, this has its own problem... explained in the next section

2. Base typescript-resolvers plugin is not supporting Interface mappers at the moment

With the setup in (1.), here's what's happening:

a. We can return UserMapper to User node:

// src/schema/Query/user.ts
export const user: NonNullable<QueryResolvers['user']> = async (
  _parent,
  _arg,
  _ctx
) => {
  // we can now return `UserMapper` here 🎉
  return {
    id: "123",
    isAdmin: true,
  };
};

b. And we can access UserMapper in UserResolver:

export const User: UserResolvers = {
  // typeof Parent is UserMapper 🎉
  __resolveType: (parent) => {
    if (parent.isAdmin) {
      return "AdminUser";
    }
    return "CustomerUser";
  },
};

c. HOWEVER, the parent of AdminUser and CustomerUser are not UserMapper

export const AdminUser: AdminUserResolvers = {
  // ❌ parent should be `UserMapper` but it's `AdminUser`
  adminName: (parent) => {
  },
};

Possible fix

To make the parent of (c.) UserMapper, we'd have to again manually wire up the type e.g.

// src/schema/schema.mappers.ts
export type UserMapper = { id: string; isAdmin: boolean };
export type AdminUserMapper = UserMapper
export type CustomerUserMapper = UserMapper

So, it's basically back to the other way in https://github.com/eddeee888/graphql-code-generator-plugins/issues/266 😅 Note that the good thing about the way where we declare AdminUserMapper and CustomerUserMapper is that any other resolver return AdminCustomer or CustomerUser node also need to return mapper, which makes the parent type consistent e.g.

Query {
  adminUser: AdminUser
}

Then, the Query.adminUser is like this:

export const adminUser = () => {
  return something // `something` is `AdminUserMapper`
}
nikhilgupta345 commented 6 months ago

Thanks for digging in! I'm not able to actually get the mapper working properly. This is my codegen.ts:

const config: CodegenConfig = {
  overwrite: true,
  schema: "**/*.graphql",
  generates: {
    "src/graphql": {
      preset: "@eddeee888/gcg-typescript-resolver-files" as any,
      presetConfig: {
        mode: "merged",
        resolverGeneration: "all",
        typesPluginsConfig: {
          contextType: "./context#Context",
          resolversNonOptionalTypename: false,
          mappers: {
            Integration: "./integration/schema.mappers.ts#IntegrationMapper",
          },
        },
        scalarsOverrides: {
          ID: {
            type: "number",
          },
        },
      },
    },
  },
  hooks: {
    afterOneFileWrite: ["prettier --config ../.prettierrc --write"],
  },
};

In this case the base type is Integration (not User) but there's no __resolveType on the IntegrationResolver. Any idea what's wrong? It's also created a ton of additional type resolvers in the root rather than within their constituent folders. (e.g. I would expect Integration.ts to be within the integration folder)

eddeee888 commented 6 months ago

Hi @nikhilgupta345 ,

mode: "merged" will put all the files at root. Doc.

For why IntegrationResolvers resolvers doesn't have __resolveType, I'm confused 🤔 I'm trying a setup and it seems to work ok.

Maybe you could send the minimal schema snippet here and I could git it a go?

eddeee888 commented 4 months ago

Hi @nikhilgupta345 ,

Apologies for the long delay! I have released an alpha version to correctly detect and wire up interface mappers:

yarn add -D @eddeee888/gcg-typescript-resolver-files@pr314-run570-1

Here's the PR with the alpha version to your repro repo: https://github.com/nikhilgupta345/repro-graphql-codegen-bug/pull/1 And here's the screenshot where parent is UserMapper:

Screenshot 2024-07-05 at 10 51 03 pm