Graphql-code-generator: [1.0] How do I reuse the types of a client-side query?

Created on 28 Mar 2019  路  28Comments  路  Source: dotansimha/graphql-code-generator

I'm using GraphQL Code Generator with the Angular template plugin.

I can write a query on client side, and it'll generate the code something like this

export type UsersQuery = { __typename?: "Query" } & {
  users: Maybe<
    Array<
      { __typename?: "User" } & Pick<
        User,
        "id" | "created" | "deleted"
      > & {
          userProperties: Array<
            { __typename?: "UserProperty" } & Pick<
              UserProperty,
              "userName" | "additionalData" | "address"
            >
          >;
        }
    >
  >;
};

In another part of the codebase, I want to reuse the data type of user which were generated from the client-query. Something like this selectedUser: UsersQuery.User;. But since the type of that single user was defined as part of the UsersQuery array, I cannot really reuse this typing information. So is there a way I can reuse these types? Thanks!

waiting-for-release

Most helpful comment

Hi @dotansimha

Amazing work, I am able to upgrade a large project with over (so-far) 150 different and specialised queries, using the compatibility plugin with little no no code changes. The usage of the types generated for each query is crucial to us, as they are passed down the tree, so without this we had to stick to the older version.

However, I am a bit worried about the name of the package "compatibility". I would really hope this case is "forever" supported. The word "compatibility" just gives me a feeling it os only for a period.

Are you going to maintain this package for the longer run? Maybe we could name it something like typescript-explicit-types or something.

Let me know if I can be at any assistance and enjoy your easter.

Best,
Jan

All 28 comments

I just upgraded to 1.0 and had the same question.

It is possible to use this as a type:

UsersQuery['users'][0]

e.g.

interface FooProps {
  user: UsersQuery['users'][0]
}

but it's not ideal. I think the previous setup (UserQuery.User) was easier to handle.

+1 on a more ideal solution; it would be nice to have a more sane-looking type scheme.

This is the reason I can not upgrade our codebase to 1.* as we use the types across the components. We use almost everywhere:

export interface IUnitsNeedingAttentionsProps {
  units: GetUnitsThatNeedsAttention.Units[];
  onClick?: (unit: GetUnitsThatNeedsAttention.Units) => void;
}

And with this change, it is impossible to re-use the same types from the query.

This is the exact problem I am facing. Cannot upgrade to 1.x as return types are used everywhere in code base.

@dotansimha what do you think of changing this back to the explicit types used for the queries?

@janhartmann @manV @emadow @elado There were a few discussions regarding this issue before.

We decided to drop the old usage for some reasons:

  • It was very hard to maintain and develop - there were a lot of issues about mismatches, invalid syntax and more. Adding new features was impossible, and fixing bugs took too long.
  • The output was not usable with babel-typescript because of namespaces.
  • Naming conflicts.

We decided to go with a single type per operation, and avoid the need to group a lot of types under a namespace.

At the moment, the best solution (and also, best practice for GraphQL) it to use Fragments.
Each fragments is being handled as an operation, so you'll have a TypeScript type for each fragment.
This way you'll be able to point your code to a specific fragment instead of a specific nested-type.

We are thinking about creating a compatibility plugin that will generate a similar namespace with types, and will point to the new generated types, but we are still not sure if that's the best path because we are trying to evolve the generated code. (you can also create your own plugin that will create compatible types according to your needs, see my comment here: https://github.com/dotansimha/graphql-code-generator/issues/1583#issuecomment-477175269)

The fragments is unfortunally not a solution for us.

We have very specific queries for all our data components where only the exact properties are fetched for it (highly nested tree also)

I would love to see the namespace option back as you also mention. If not, we will stick to the pre 1.0 version.

Thanks for the hard work so far!

@janhartmann Can you please share you use case? Any specific reason not to use fragments?
And sure, we'll update soon if we'll see that's it not a big deal to add the backward compatibility support.

Using fragment is fine for me as I'm using code generator v1 on a new project. But it might not be an ideal solution in some cases, as you have to create a fragment for your query in order to reuse the types, regardless of whether you really intend to share the fragment or not. But this doesn't really bother me.

I think it's a good idea having a compatibility plugin to support other people using old versions.

Yea, appreciate for all your hard work, it's a great project!

@ezpub @janhartmann I started to experiment with the compatibility plugin. Still not sure that we'll be able to cover all use cases.
WIP here: https://github.com/dotansimha/graphql-code-generator/pull/1621
Example for output here: https://github.com/dotansimha/graphql-code-generator/pull/1621/files#diff-6e5a61a30f601f6d18070c4f82a44dcbR80

Great @dotansimha, I will have a look!

Update:
PR https://github.com/dotansimha/graphql-code-generator/pull/1621 almost ready, should cover most of the use-cases.

@janhartmann @ezpub
We merged the new compatibility plugin, any chance you can try it with your project (1.0.8-alpha-87bc637b.10)?
We would like to get more use-cases before releasing it.

If you can wait for tomorrow? Working on some other features on the project right now. But I will get back to you asap.

@janhartmann Sure no problem, we plan to release it in a few days.

Thanks @dotansimha for all the work you are putting into this project.

I just ran the 1.0.8-alpha-87bc637b.10 though our type generation and it started to look much better:

export type BranchForNameQueryVariables = {
  repo: Scalars["String"];
  owner: Scalars["String"];
  branch: Scalars["String"];
};

export type BranchForNameQuery = { __typename?: "QueryType" } & {
  Branch: Maybe<
    Array<
      Maybe<
        { __typename?: "Branch" } & Pick<Branch, "id"> & {
            repo: Maybe<{ __typename?: "Repo" } & Pick<Repo, "id">>;
          }
      >
    >
  >;
};
export namespace BranchForName {
  export type Variables = BranchForNameQueryVariables;
}

However, I was hoping I would get export type Query = BranchForNameQuery; inside the namespace too. Is that omission intentional?

@cdupuis No, it should generate all of them, look here: https://github.com/dotansimha/graphql-code-generator/blob/master/packages/plugins/typescript-compatibility/tests/compatibility.spec.ts#L95-L119

Can you please provide a small reproduction for it?

Sure thing, @dotansimha.

See https://github.com/cdupuis/graphql-code-generator-test for an example. After running the generator, you'll see the missing Query in the ./lib/typings/types.ts.

@dotansimha Thanks for making this compatibility plugin! I've been trying it out using 1.0.8-alpha-87bc637b.10 but am having some issues with nested types. Here's an example of our generated types:

export type GetAccountUsersQueryVariables = {
  id: Scalars['ID'];
};

export type GetAccountUsersQuery = { __typename?: 'Query' } & {
  account: Maybe<
    { __typename?: 'Account' } & Pick<Account, 'id' | 'name'> & {
        users: Maybe<
          Array<{ __typename?: 'User' } & Pick<User, 'id' | 'email'>>
        >;
      }
  >;
};

export namespace GetAccountUsers {
  export type Variables = GetAccountUsersQueryVariables;
  export type Query = GetAccountUsersQuery;
  export type Account = GetAccountUsersQuery['account'];
  export type Users = GetAccountUsersQuery['account']['users'][0];
}

I'm seeing a typescript error on lines like export type Users = GetAccountUsersQuery['account']['users'][0];.
Error text is: Property 'users' does not exist on type 'Maybe<{ __typename?: "Account" | undefined; } & Pick<Account, "id" | "name"> & { users: Maybe<({ __typename?: "User" | undefined; } & Pick<User, "id" | "email">)[]>; }>'. A similar error is occurring with the [0] property references in type aliases. Would this be an issue with the plugin or is this to do with our config?

@dotansimha Thanks, just for your information, the compatibility plugin v1.0.8-alpha-87bc637b.10 works fine on my machine, it generates what I expected :D

@dotansimha further details to the above repo to reproduce this:

It appears the call in https://github.com/dotansimha/graphql-code-generator/blob/master/packages/plugins/typescript-compatibility/src/visitor.ts#L31 fails to locate the corresponding type in the schema as referenced by typeName.

If I change

    "queryType": {
      "name": "QueryType"
    },

to

    "queryType": {
      "name": "Query"
    },

it works and I get the expected query and types generated:

export namespace BranchForName {
  export type Variables = BranchForNameQueryVariables;
  export type query = BranchForNameQuery;
  export type Branch = BranchForNameQuery["Branch"][0];
  export type repo = BranchForNameQuery["Branch"][0]["repo"];
}

@paulpjryan Can you please share your tsconfig file?
I think it's related to strict mode of TypeScript.

Without strict mode:
image

With strict mode:
image

I think we need to add support for it, by wrapping the proxy type with NonNullable:

export type Users = NonNullable<NonNullable<GetAccountUsersQuery['account']>['users']>[0];

I created an issue for it: https://github.com/dotansimha/graphql-code-generator/issues/1678

Thanks @cdupuis , I opened an issue for it: https://github.com/dotansimha/graphql-code-generator/issues/1679

@dotansimha That seems like a good bet. Here's a snippet of our tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2017",
    "allowJs": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "noImplicitAny": false,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "alwaysStrict": true,
    "strictNullChecks": true,
    "strictPropertyInitialization": false,
...

Switching strictNullChecks to false seems to resolve the errors I've been seeing.

Thanks @paulpjryan , I'm working on a fix.

typescript-compatibility is available in 1.1.0 馃殌

Hi @dotansimha

Amazing work, I am able to upgrade a large project with over (so-far) 150 different and specialised queries, using the compatibility plugin with little no no code changes. The usage of the types generated for each query is crucial to us, as they are passed down the tree, so without this we had to stick to the older version.

However, I am a bit worried about the name of the package "compatibility". I would really hope this case is "forever" supported. The word "compatibility" just gives me a feeling it os only for a period.

Are you going to maintain this package for the longer run? Maybe we could name it something like typescript-explicit-types or something.

Let me know if I can be at any assistance and enjoy your easter.

Best,
Jan

Hi @janhartmann
I'm glad to hear that the new plugin works for you.
We chose the name compatibility because the plugin should be in use while migrating to 1.0 types. We'll keep it updated in the upcoming future, but I guess not forever.
Using fragments should make it easier to consume the new types structure, but I think if we'll see that it becomes annoying, we might add those intermediate types again.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

leonardfactory picture leonardfactory  路  3Comments

leebenson picture leebenson  路  3Comments

mszczepanczyk picture mszczepanczyk  路  3Comments

SimenB picture SimenB  路  3Comments

quolpr picture quolpr  路  3Comments