fragments on interfaces are not exported
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:
- 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
# ...
}
# ...
- 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
}
}
- My
codegen.ymlconfig 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
Thank you for reporting this @doomsower . Can you please share a live reproduction of that issue?
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.

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.
@dotansimha Any updates on that?