dotansimha / dotansimha/graphql-code-generator
can't resolve type problem of `makeFragmentData` with nested Fragment
- Dominant language
- TypeScript
- Stars
- 11.3k
- Forks
- 1.4k
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 23
Description
### Which packages are impacted by your issue?
@graphql-codegen/client-preset
### Describe the bug
This bug is also discussed here https://github.com/dotansimha/graphql-code-generator/discussions/8554#discussioncomment-6367589.
If we have nested fragment, especially in case it has more than one children fragment from the same type, it seems that we can't resolve type problem of `makeFragmentData`.
For example, we have two fragments as like follows:
```js
const UserWithNameFragment = graphql(`
fragment UserWithName on User {
id
name
}
`);
const UserContactFragment = graphql(`
fragment UserContact on User {
id
email
}
`);
```
Then, we have another fragment which includes the fragments.
```js
const UserDataFragment = graphql(`
fragment UserData on User {
...UserWithName
...UserContact
}
`);
```
Now, we are not able to make Fragment mock data by `makeFragmentData` with `UserDataFragment` without extra type assertion.
### Your Example Website or App
https://github.com/tnyo43/graphql-code-generator-issue-fragment-conflict
### Steps to Reproduce the Bug or Issue
1. run `npm install`
2. run `npm run generate` (even though I commit everything, just to make sure)
3. open "src/User.tsx" and you will see the following error.
```
Argument of type '{ ' $fragmentRefs'?: { UserContact_UserFragment: UserContact_UserFragment; } | { UserWithAvatar_UserFragment: UserWithAvatar_UserFragment; } | undefined; }' is not assignable to parameter of type 'User_UserFragment'.
Type '{ ' $fragmentRefs'?: { UserContact_UserFragment: UserContact_UserFragment; } | { UserWithAvatar_UserFragment: UserWithAvatar_UserFragment; } | undefined; }' is not assignable to type '{ ' $fragmentRefs'?: { UserWithAvatar_UserFragment: UserWithAvatar_UserFragment; UserContact_UserFragment: UserContact_UserFragment; } | undefined; }'.
Types of property '' $fragmentRefs'' are incompatible.
Type '{ UserContact_UserFragment: UserContact_UserFragment; } | { UserWithAvatar_UserFragment: UserWithAvatar_UserFragment; } | undefined' is not assignable to type '{ UserWithAvatar_UserFragment: UserWithAvatar_UserFragment; UserContact_UserFragment: UserContact_UserFragment; } | undefined'.
Property ''UserWithAvatar_UserFragment'' is missing in type '{ UserContact_UserFragment: UserContact_UserFragment; }' but required in type '{ UserWithAvatar_UserFragment: UserWithAvatar_UserFragment; UserContact_UserFragment: UserContact_UserFragment; }'.ts(2345)
```
### Expected behavior
I expected that there is a good way to make fragment data without difficulty. Now we need to add extra any to resolve type problem.
```ts
// it works but needs type assertion :(
const mockData = makeFragmentData(
{
id: "user_1",
username: "tom",
avatarUrl: "tom.png",
email: 'mail@mail.com'
} as any,
User_UserFragment
);
```
### Screenshots or Videos

### Platform
- OS: [e.g. macOS, Windows, Linux]
- NodeJS: 18.16.0
- `graphql` version: 16.2.0
- `@graphql-codegen/*` version(s): 4.0.1
### Codegen Config File
```ts
// codegen.ts
import { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {
schema: "schema.graphql",
documents: "src/**/*.tsx",
generates: {
"src/gql/": { preset: "client" },
},
noSilentErrors: true,
};
export default config;
```
### Additional context
`makeFragmentData` requires to be nested itself if the target Fragment is nested. I think that is the root cause of the problem.
The implementation of `makeFragmentData` is follows. The `data` type is not unmasked well, so it requires `makeFragmentData` itself inside the `data` if the Fragment is nested.
```ts
export function makeFragmentData<
F extends DocumentTypeDecoration,
FT extends ResultOf
>(data: FT, _fragment: F): FragmentType {
return data as FragmentType;
}
```
---
One possible solution is to use something like `UnmaskFragmentData` type for the type of `data`.
```
export function makeFragmentData<
F extends DocumentTypeDecoration
>(data: UnmaskFragment, _fragment: F): FragmentType {
return data as FragmentType;
}
```
I found a challenge to make an utility type to unmask Fragment type (https://github.com/dotansimha/graphql-code-generator/pull/9380), but it is not going well.
---
I think a solution is to create overloads of `makeFragmentData` as like `graphql` function.
We can make unmask types of each Fragment with [typescript-operations](https://the-guild.dev/graphql/codegen/plugins/typescript/typescript-operations) plugin.
So we can defing `makeFragmentData` as like follows not to require itself inside of it:
```ts
function makeFragmentData(data: UserWithNameFragment, _fragment: typeof UserWithAvatar_UserFragmentDoc): FragmentType {
return data as FragmentType
}
function makeFragmentData(data: UserContactFragment, _fragment: typeof UserContact_UserFragment): FragmentType {
return data as FragmentType
}
function makeFragmentData(data: UserDataFragment, _fragment: typeof User_UserFragment): FragmentType {
return data as FragmentType
}
...
```
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.