dotansimha / dotansimha/graphql-code-generator

Incomplete interface fragment coverage generates types vulnerable to non-breaking schema changes

Open
#10,607 0 comments 1 reaction 1 assignee Claimed by @eddeee888 View on GitHub
Dominant language
TypeScript
Stars
11.3k
Forks
1.4k
Avg merge
1d 1h
Merged PRs (30d)
23

Description

## 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
```graphql
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
```graphql
fragment SearchableReducedModuleFields on SearchableModule {
finderCollection {
... on MotorsCompatibilityFinderCollection {
searchRefinement {
refinementSelectors {
name
}
}
}
}
}
```

### Generated Types (Before Schema Change)
With only one implementation of `FinderCollectionSearchableModule`, codegen generates:
```typescript
type finderCollection = {
__typename: 'MotorsCompatibilityFinderCollection';
searchRefinement: { refinementSelectors: Array<{ name: string }> };
} | null;
```

### Code Using These Types
```typescript
// This compiles fine
const selectors = data.finderCollection?.searchRefinement?.refinementSelectors;
```

### Non-Breaking Schema Change
A new type is added that implements the same interface:
```graphql
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:
```typescript
type finderCollection =
| { __typename: 'MotorsCompatibilityFinderCollection'; searchRefinement: {...} }
| { __typename: 'LiveEventsFinderCollection' } // No searchRefinement field!
| null;
```

### Result
The exact same code now fails type checking:
```typescript
// 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:

1. **Warn/error** when inline fragments don't cover all possible implementations of an interface
2. **Generate defensive types** that account for unknown implementations, e.g.:
```typescript
type finderCollection =
| { __typename: 'MotorsCompatibilityFinderCollection'; searchRefinement: {...} }
| { __typename: string } // unknown implementation
| null;
```
3. **Provide a configuration option** like `strictInterfaceFragments` to enforce exhaustive coverage
4. **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

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.