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

[@graphql-codegen/typescript] Support for __is<AbstractType> field in Relay #9666

Closed
mizdra opened this issue Sep 9, 2023 · 2 comments
Closed

Comments

@mizdra
Copy link
Contributor

mizdra commented Sep 9, 2023

Is your feature request related to a problem? Please describe.

I am using msw to mock the request from Relay Client and return a dummy response. In order to help creating dummy responses, I developed graphql-fabbrica, which is a graphql-code-generator's plugin. This plugin allows users to create dummy data that matches the type definitions generated by @graphql-codegen/typescript. I use this to create dummy responses.

# schema.graphql
type Book {
  id: ID!
  title: String!
  author: Author!
}
type Author {
  id: ID!
  name: String!
  books: [Book!]!
}
type Query {
  book(id: ID!): Book
}
// __generated__/types.ts (generated by @graphql-codegen/typescript)
// ...
export type Author = {
  __typename: 'Book';
  books: Array<Book>;
  id: Scalars['ID']['output'];
  name: Scalars['String']['output'];
};

export type Book = {
  __typename: 'Author';
  author: Author;
  id: Scalars['ID']['output'];
  title: Scalars['String']['output'];
};
// src/factory.ts
import { defineBookFactory, dynamic } from '../__generated__/fabbrica.js';

export const BookFactory = defineBookFactory({
  defaultFields: {
    __typename: 'Book',
    id: dynamic(({ seq }) => `Book-${seq}`),
    title: 'Yuyushiki',
    author: undefined,
  },
});

// const book = await BookFactory.build();
// expect(book).toStrictEqual({
//   __typename: 'Book',
//   id: 'Book-0',
//   title: expect.any(String),
//   author: undefined,
// });
// assertType<{
//   __typename: 'Book';
//   id: string;
//   title: string;
//   author: undefined;
// }>(book);
// src/case-1.tsx
import { graphql } from 'react-relay';
import { type case1_Query$rawResponse } from './__generated__/case1_Query.graphql.js';
import { BookFactory } from './factory.js';

graphql`
  query case1_Query @raw_response_type {
    book(id: "1") {
      __typename
      title
    }
  }
`;

// Define a dummy response with graphql-fabbrica.
const dummyResponse: case1_Query$rawResponse = {
  book: await BookFactory.build(),
};

The above example works well. However, a problem occurs when trying to define a dummy response for a query that returns a union or interface.

# schema.graphql
interface Node {
  id: ID!
}
type Book implements Node {
  id: ID!
  title: String!
  author: Author!
}
type Author implements Node {
  id: ID!
  name: String!
  books: [Book!]!
}
union SearchResult = Book | Author
type Query {
  book(id: ID!): Book
  node(id: ID!): Node
  search(query: String!): [SearchResult!]!
}
// src/case-2.tsx
import { graphql } from 'react-relay';
import { type case2_Query$rawResponse } from './__generated__/case2_Query.graphql.js';
import { BookFactory } from './factory.js';

graphql`
  query case2_Query @raw_response_type {
    node(id: "1") {
      __typename
      ... on Node {
        id
      }
    }
    search(query: "foo") {
      __typename
      ... on SearchResult {
        __typename
      }
    }
  }
`;

// Define a dummy response with graphql-fabbrica.
const dummyResponse: case2_Query$rawResponse = {
  node: await BookFactory.build(),
  search: [await BookFactory.build()],
};

When this code is type-checked by tsc, the following type errors are reported.

$ npm run lint:tsc

> app_name@0.0.0 lint:tsc
> tsc

src/case-2.tsx:24:3 - error TS2741: Property '__isNode' is missing in type 'Pick<Merge<ResolvedFields<{ __typename: "Book"; id: Dynamic<Book, string>; title: string; author: undefined; }>, ResolvedFields<{}>>, "__typename" | ... 2 more ... | "title">' but required in type '{ readonly __typename: string; readonly __isNode: string; readonly id: string; }'.

24   node: await BookFactory.build(),
     ~~~~

  src/__generated__/case2_Query.graphql.ts:25:14
    25     readonly __isNode: string;
                    ~~~~~~~~
    '__isNode' is declared here.
  src/__generated__/case2_Query.graphql.ts:23:12
    23   readonly node: {
                  ~~~~
    The expected type comes from property 'node' which is declared here on type 'case2_Query$rawResponse'

src/case-2.tsx:25:12 - error TS2739: Type 'Pick<Merge<ResolvedFields<{ __typename: "Book"; id: Dynamic<Book, string>; title: string; author: undefined; }>, ResolvedFields<{}>>, "__typename" | ... 2 more ... | "title">' is missing the following properties from type '{ readonly __typename: string; readonly __isNode: string; readonly __isSearchResult: string; readonly id: string; }': __isNode, __isSearchResult

25   search: [await BookFactory.build()],
              ~~~~~~~~~~~~~~~~~~~~~~~~~


Found 2 errors in the same file, starting at: src/case-2.tsx:24

This is caused by the type definitions generated by the relate-compiler.

// src/__generated__/case2_Query.graphql.ts
// ...
export type case2_Query$rawResponse = {
  readonly node: {
    readonly __typename: string;
    readonly __isNode: string;
    readonly id: string;
  } | null;
  readonly search: ReadonlyArray<{
    readonly __typename: string;
    readonly __isNode: string;
    readonly __isSearchResult: string;
    readonly id: string;
  }>;
};

The relay-compiler converts the original query to one with the field __is<AbstractType> added so that the Relay Client can determine if the type implements the interface (more details: facebook/relay#3129 (comment), repo:facebook/relay /__is<AbstractType>/).

graphql-fabbrica cannot generate data that passes type checking because this __is<AbstractType> is not included in the type definition generated by @graphql-codegen/typescript.

Describe the solution you'd like

I would like to add the following two options to @graphql-codegen/typescript.

  • skipIsAbstractType (type: boolean, default: true)
  • nonOptionalIsAbstractType (type: boolean, default: false)

They are similar to the skipTypename, nonOptionalTypename option. If skipIsAbstractType === false, add __is<AbstractType> to the type definition, if nonOptionalIsAbstractType === true, make the field optional.

// skipIsAbstractType === false, nonOptionalIsAbstractType === false
export type Book = {
  __typename: 'Author';
  __isNode: 'Author';
  __isSearchResult: 'Author';
  author: Author;
  id: Scalars['ID']['output'];
  title: Scalars['String']['output'];
};
// skipIsAbstractType === false, nonOptionalIsAbstractType === true
export type Book = {
  __typename: 'Author';
  __isNode?: 'Author';
  __isSearchResult?: 'Author';
  author: Author;
  id: Scalars['ID']['output'];
  title: Scalars['String']['output'];
};

Describe alternatives you've considered

No response

Is your feature request related to a problem? Please describe.

Demo codes: https://github.com/mizdra/example-relay-is-abstruct-type

@mizdra
Copy link
Contributor Author

mizdra commented Sep 9, 2023

If this proposal is acceptable, I am going to create a Pull Request. Is this proposal acceptable?

@mizdra mizdra changed the title [@graphql-codegen/typescript] Support for __is<AbstractType> field in relays [@graphql-codegen/typescript] Support for __is<AbstractType> field in relay Sep 9, 2023
@mizdra mizdra changed the title [@graphql-codegen/typescript] Support for __is<AbstractType> field in relay [@graphql-codegen/typescript] Support for __is<AbstractType> field in Relay Sep 9, 2023
@mizdra
Copy link
Contributor Author

mizdra commented Sep 26, 2023

I realized that I could achieve my goal without fixing graphql-code-generator, just by fixing graphql-fabbrica.

So this feature request is no longer needed by me, and I close the issue. If anyone wants this feature, please reopen it :)

@mizdra mizdra closed this as completed Sep 26, 2023
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

No branches or pull requests

1 participant