dotansimha / dotansimha/graphql-code-generator

can't resolve type problem of `makeFragmentData` with nested Fragment

Open
#9,702 2 comments 19 reactions 0 assignees View on GitHub
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

![Screenshot 0005-10-07 at 11 13 52](https://github.com/dotansimha/graphql-code-generator/assets/11014018/d66cb75a-7514-4501-b994-f4bbf78ee5d6)

### 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.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.