Description
When querying a field that returns an interface type, if the query uses inline fragments that don't cover all possible implementations, GraphQL Codegen generates types that are fragile to non-breaking schema changes.
Specifically: if the schema later adds a new type implementing the interface (a non-breaking change), code that previously type-checked will suddenly fail, even though the query itself remains valid and the schema change was non-breaking.
Reproduction
Initial Schema
interface FinderCollectionSearchableModule {
searchRefinement: SearchRefinement
# ... other fields
}
type MotorsCompatibilityFinderCollection implements FinderCollectionSearchableModule {
searchRefinement(input: MotorsSearchRefinementInput): SearchRefinement
# ... other fields
}
type SearchableModule {
finderCollection: FinderCollectionSearchableModule
}
Query with Incomplete Fragment Coverage
fragment SearchableReducedModuleFields on SearchableModule {
finderCollection {
... on MotorsCompatibilityFinderCollection {
searchRefinement {
refinementSelectors {
name
}
}
}
}
}
Generated Types (Before Schema Change)
With only one implementation of FinderCollectionSearchableModule, codegen generates:
type finderCollection = {
__typename: 'MotorsCompatibilityFinderCollection';
searchRefinement: { refinementSelectors: Array<{ name: string }> };
} | null;
Code Using These Types
// This compiles fine
const selectors = data.finderCollection?.searchRefinement?.refinementSelectors;
Non-Breaking Schema Change
A new type is added that implements the same interface:
type LiveEventsFinderCollection implements FinderCollectionSearchableModule {
searchRefinement: SearchRefinement # Same field, different signature
# ... other fields
}
This is a non-breaking change - it only adds a new possible type.
Generated Types (After Schema Change)
Now codegen generates:
type finderCollection =
| { __typename: 'MotorsCompatibilityFinderCollection'; searchRefinement: {...} }
| { __typename: 'LiveEventsFinderCollection' } // No searchRefinement field!
| null;
Result
The exact same code now fails type checking:
// TypeScript error: Property 'searchRefinement' does not exist on type
// '{ __typename: 'LiveEventsFinderCollection' }'
const selectors = data.finderCollection?.searchRefinement?.refinementSelectors;
The Problem
The original query had incomplete fragment coverage, but GraphQL Codegen generated types as if the set of implementations was closed/fixed. When a new implementation was added (non-breaking change), the types changed in a breaking way.
Expected Behavior
GraphQL Codegen should handle incomplete interface fragment coverage in one of these ways:
- Warn/error when inline fragments don't cover all possible implementations of an interface
- Generate defensive types that account for unknown implementations, e.g.:
type finderCollection =
| { __typename: 'MotorsCompatibilityFinderCollection'; searchRefinement: {...} }
| { __typename: string } // unknown implementation
| null;
- Provide a configuration option like
strictInterfaceFragments to enforce exhaustive coverage
- Generate a union that includes a catch-all for unqueried implementations
The key principle: types should be resilient to non-breaking schema changes. Adding a new type that implements an existing interface should not break existing type-checked code.
Environment
@graphql-codegen/cli: 5.0.5
@graphql-codegen/typescript: 4.1.5
@graphql-codegen/typescript-operations: 4.5.1
Description
When querying a field that returns an interface type, if the query uses inline fragments that don't cover all possible implementations, GraphQL Codegen generates types that are fragile to non-breaking schema changes.
Specifically: if the schema later adds a new type implementing the interface (a non-breaking change), code that previously type-checked will suddenly fail, even though the query itself remains valid and the schema change was non-breaking.
Reproduction
Initial Schema
Query with Incomplete Fragment Coverage
Generated Types (Before Schema Change)
With only one implementation of
FinderCollectionSearchableModule, codegen generates:Code Using These Types
Non-Breaking Schema Change
A new type is added that implements the same interface:
This is a non-breaking change - it only adds a new possible type.
Generated Types (After Schema Change)
Now codegen generates:
Result
The exact same code now fails type checking:
The Problem
The original query had incomplete fragment coverage, but GraphQL Codegen generated types as if the set of implementations was closed/fixed. When a new implementation was added (non-breaking change), the types changed in a breaking way.
Expected Behavior
GraphQL Codegen should handle incomplete interface fragment coverage in one of these ways:
strictInterfaceFragmentsto enforce exhaustive coverageThe key principle: types should be resilient to non-breaking schema changes. Adding a new type that implements an existing interface should not break existing type-checked code.
Environment
@graphql-codegen/cli: 5.0.5@graphql-codegen/typescript: 4.1.5@graphql-codegen/typescript-operations: 4.5.1