dotansimha / graphql-code-generator

A tool for generating code based on a GraphQL schema and GraphQL operations (query/mutation/subscription), with flexible support for custom plugins.
https://the-guild.dev/graphql/codegen/
MIT License
10.86k stars 1.33k forks source link

fragments on interfaces are not exported #6090

Open doomsower opened 3 years ago

doomsower commented 3 years ago

Describe the bug

I have a monorepo in which I define schema and common fragments in one package `@myorg/schema`, and then use the schema and these fragments in other packages. The schema contains interface, implementing types and a fragment on this interface (see below). Generated fragment in `@myorg/schema` packages produces following code: ```ts type TimestampedMeta_Media_Fragment = { __typename?: 'Media'; createdAt?: Types.Maybe; updatedAt?: Types.Maybe; }; type TimestampedMeta_Descent_Fragment = { __typename?: 'Descent'; createdAt?: Types.Maybe; updatedAt?: Types.Maybe; }; // ... export type TimestampedMetaFragment = | TimestampedMeta_Region_Fragment | TimestampedMeta_River_Fragment | TimestampedMeta_Section_Fragment | TimestampedMeta_Gauge_Fragment | TimestampedMeta_Source_Fragment | TimestampedMeta_User_Fragment | TimestampedMeta_Media_Fragment | TimestampedMeta_Descent_Fragment; ``` Note that types like `TimestampedMeta_Region_Fragment` are not exported. I another package I have a query with `TimestampedMeta` fragment, and code generator produces following code: ```ts import { TimestampedMeta_Region_Fragment, TimestampedMeta_River_Fragment, TimestampedMeta_Section_Fragment, TimestampedMeta_Gauge_Fragment, TimestampedMeta_Source_Fragment, TimestampedMeta_User_Fragment, TimestampedMeta_Media_Fragment, TimestampedMeta_Descent_Fragment, } from '@myorg/schema'; // ... export type SectionDetailsFragment = { __typename?: 'Section'; description?: Types.Maybe; shape: Array>; region?: Types.Maybe<{ __typename?: 'Region'; id: string }>; } & SectionCoreFragment & SectionEndsFragment & SectionMeasurementsFragment & SectionPoIsFragment & SectionTagsFragment & SectionLicenseFragment & TimestampedMeta_Section_Fragment; ``` Note that all the variations of `TimestampedMeta` fragment are imported, except for the one which is really exported. **To Reproduce** Steps to reproduce the behavior:
  1. My GraphQL schema (defined in @myorg/schema):

interface Timestamped {
  createdAt: Date
  updatedAt: Date
}

# ...

type Section implements NamedNode & Timestamped {
  id: ID!
  name: String!
  createdAt: Date
  updatedAt: Date
 # ...
}

# ...
  1. My GraphQL operations:

This fragment is defined in @myorg/schema package

fragment TimestampedMeta on Timestamped {
  createdAt
  updatedAt
}

This query is defined in @myorg/clients package

fragment SectionDetails on Section {
  ...SectionCore
  # ... more fragments
  ...TimestampedMeta
}

query sectionDetails($sectionId: ID) {
  section(id: $sectionId) {
    ...SectionDetails
  }
}
  1. My codegen.yml config file:
overwrite: true
schema:
  - './packages/schema/schema/*.graphql'
documents:
  # Shared fragments, used both by frontend and by backend tests
  - ./packages/schema/fragments/*.gql
generates:
  # Common types shared by backend, web frontend and mobile are generated in schema package
  # Common validation schemas are also in schema package
  ./packages/schema/src/__generated__/types.ts:
    plugins:
      - typescript
      - add:
          content: '/* eslint-disable @typescript-eslint/no-explicit-any */'
    config:
      scalars:
        Date: Date
        DateTime: Date
        JSON: '{ [key: string]: any }'

  # Typedefs are used to make executable schema on backend
  ./packages/schema/src/__generated__/typeDefs.ts:
    plugins:
      - add:
          content: '/* eslint-disable @typescript-eslint/no-explicit-any */'
      - ./packages/schema/plugins/codegen-typedefs.js

  ./packages/schema/src/__generated__/fragments.ts:
    preset: import-types
    presetConfig:
      typesPath: ./types
    documents:
      - ./packages/schema/fragments/*.gql
    plugins:
      - typescript-operations
      - typescript-document-nodes
      - add:
          content: '/* eslint-disable @typescript-eslint/no-explicit-any */'
    config:
      # Setting this suffix will allow inports using 'importAllFragmentsFrom'
      fragmentSuffix: 'FragmentDoc'

  # GRAPHQL resolvers
  ./packages/backend/src/apollo/resolvers.generated.ts:
    preset: import-types
    presetConfig:
      typesPath: '@myorg/schema'
    plugins:
      - add:
          content:
            - '/* eslint-disable @typescript-eslint/no-explicit-any */'
      - typescript-resolvers
    config:
      useIndexSignature: true
      noSchemaStitching: true
      contextType: ./context#Context
      resolverTypeWrapperSignature: 'T extends object ? Promise<Partial<T>> | Partial<T> : Promise<T> | T'
      defaultMapper: any
      mappers:
        Banner: ~/db#Sql.Banners
        BannerSource: ~/db#Sql.BannerSource
        Descent: ~/db#Sql.Descents
        Gauge: ~/db#Sql.GaugesView
        GaugeBinding: ~/db#Sql.GaugeBinding
        Group: ~/db#Sql.GroupsView
        License: '@myorg/schema#License'
        Media: ~/db#Sql.MediaView
        Point: ~/db#Sql.PointsView
        Region: ~/db#Sql.RegionsView
        RegionCoverImage: ~/db#Sql.RegionCoverImage
        River: ~/db#Sql.RiversView
        Section: ~/db#Sql.SectionsView
        Source: ~/db#Sql.SourcesView
        Suggestion: ~/db#Sql.Suggestions
        User: ~/db#Sql.Users
      scalars:
        Date: Date
        DateTime: Date
        JSON: unknown

  # Tests that run GRAPHQL queries and mutations against our API
  ./packages/backend/src/:
    documents:
      - ./packages/backend/src/**/__tests__/*.ts
      - ./packages/backend/src/**/*.test.ts
    preset: near-operation-file
    presetConfig:
      extension: .generated.ts
      baseTypesPath: '~@myorg/schema'
      importAllFragmentsFrom: '~@myorg/schema'
    plugins:
      - typescript-operations
      # This custom-made plugin generates strongly typed functions to execute graphql queries in tests
      - '@myorg/codegen-backend-tests'

  # Frontend queries and mutations
  ./:
    documents:
      - ./packages/clients/src/**/*.gql
      - ./packages/mobile/src/**/*.gql
      - ./packages/web/src/**/*.gql
    preset: near-operation-file
    presetConfig:
      extension: .generated.ts
      baseTypesPath: '~@myorg/schema'
      importAllFragmentsFrom: '~@myorg/schema'
    plugins:
      - add:
          content: '/* eslint-disable @typescript-eslint/no-explicit-any */'
      - typescript-operations
      - typescript-react-apollo
    config:
      withComponent: false
      withHOC: false
      withHooks: true
      withMutationFn: false
      reactApolloVersion: 2
      scalars:
        Date: string
        DateTime: string
        JSON: unknown

config:
  preResolveTypes: true

hooks:
  afterAllFileWrite:
    - prettier --write
  beforeDone:
    # These files are generated by near-operation-file and are same as generated fragments.ts
    # They are generated because we want to use fragments in backend/frontend queries
    - 'rm -rf ./packages/schema/fragments/*.ts'

Expected behavior

I expect types like TimestampedMeta_Media_Fragment to be exported, so that I can consume them in another package.

Environment:

  • OS:
    "@graphql-codegen/cli": "^1.21.5",
    "@graphql-codegen/import-types-preset": "^1.18.2",
    "@graphql-codegen/near-operation-file-preset": "^1.18.1",
    "@graphql-codegen/typescript": "^1.22.1",
    "@graphql-codegen/typescript-document-nodes": "^1.17.12",
    "@graphql-codegen/typescript-operations": "^1.18.0",
    "@graphql-codegen/typescript-react-apollo": "^2.2.5",
    "@graphql-codegen/typescript-resolvers": "^1.19.2",
  • NodeJS: 14

Additional context

dotansimha commented 3 years ago

Thank you for reporting this @doomsower . Can you please share a live reproduction of that issue?

pachuka commented 3 years ago

So I think I have been running into the same issue. The key here is we have a union type that we spread/use within a fragment. This generates a new fragment type that ends up being not exported. @dotansimha I've created a simple reproduction (probably could be reduced more, but this matches my usecase). https://github.com/pachuka/graphql-union-exports

Basically I have a fragment that spreads a union type of PersonalLinesCustomer and BusinessLinesCustomer and you can see in the client.ts that the generated CustomerBusinessLinesCustomerFragment and CustomerPersonalLinesCustomerFragment are not exported so they can't be used explicitly when using discriminating unions.

image

NOTE: the fact that I'm using an existing fragment definition inside my spread doesn't seem to matter much, which is also interesting, but can reproduce this scenario without that as well.

In certain scenarios I know its only going to be one of the implementations so I want to be able to cast it explicitly as that (even though arguably that's unsafe), but since those generated fragment types are not exported, I am unable to do that.

zgrybus commented 10 months ago

@dotansimha Any updates on that?