graphql-code-generator icon indicating copy to clipboard operation
graphql-code-generator copied to clipboard

fragments on interfaces are not exported

Open doomsower opened this issue 4 years ago • 3 comments

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:


type TimestampedMeta_Media_Fragment = {
  __typename?: 'Media';
  createdAt?: Types.Maybe<any>;
  updatedAt?: Types.Maybe<any>;
};

type TimestampedMeta_Descent_Fragment = {
  __typename?: 'Descent';
  createdAt?: Types.Maybe<any>;
  updatedAt?: Types.Maybe<any>;
};

// ...

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:

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<string>;
  shape: Array<Array<number>>;
  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

doomsower avatar Jun 06 '21 06:06 doomsower

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

dotansimha avatar Jun 20 '21 09:06 dotansimha

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.

pachuka avatar Oct 03 '21 20:10 pachuka

@dotansimha Any updates on that?

zgrybus avatar Jan 12 '24 11:01 zgrybus