Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NexusGenFieldTypes with resolved fields #36

Open
ecwyne opened this issue Feb 14, 2019 · 6 comments
Open

NexusGenFieldTypes with resolved fields #36

ecwyne opened this issue Feb 14, 2019 · 6 comments
Labels
question Further information is requested type/docs Relates to knowledge transfer matter (refs, guides, tuts, examples, ...)

Comments

@ecwyne
Copy link

ecwyne commented Feb 14, 2019

I am not sure how best to ask this question, so I hope this simplest reproduction makes sense.

This code

// SDL
// type User {
//     name: String!
//     post: Post!
// }
// type Post {
//     body: String!
//     author: User!
// }
// type Query {
//     user: User!
// }
const User = objectType({
    name: 'User',
    definition: t => {
        t.string('name');
        t.field('post', {
            type: Post,
            resolve: (): any => ({ body: 'hello' }),
        });
    },
});

const Post = objectType({
    name: 'Post',
    definition: t => {
        t.string('body');
        t.field('author', {
            type: User,
            resolve: (): any => ({ name: 'Johannes' }),
        });
    },
});

const Query = objectType({
    name: 'Query',
    definition: t => {
        t.field('user', {
            type: User,
            resolve: () => ({ name: 'Johannes' }),
        });
    },
});

produces these ts type definitions

declare global {
  interface NexusGen extends NexusGenTypes {}
}

export interface NexusGenInputs {
}

export interface NexusGenEnums {
}

export interface NexusGenRootTypes {
  Post: { // root type
    body: string; // String!
  }
  Query: {};
  User: { // root type
    name: string; // String!
  }
  String: string;
  Int: number;
  Float: number;
  Boolean: boolean;
  ID: string;
}

export interface NexusGenAllTypes extends NexusGenRootTypes {
}

export interface NexusGenFieldTypes {
  Post: { // field return type
    author: NexusGenRootTypes['User']; // User!
    body: string; // String!
  }
  Query: { // field return type
    user: NexusGenRootTypes['User']; // User!
  }
  User: { // field return type
    name: string; // String!
    post: NexusGenRootTypes['Post']; // Post!
  }
}

export interface NexusGenArgTypes {
}

export interface NexusGenAbstractResolveReturnTypes {
}

export interface NexusGenInheritedFields {}

export type NexusGenObjectNames = "Post" | "Query" | "User";

export type NexusGenInputNames = never;

export type NexusGenEnumNames = never;

export type NexusGenInterfaceNames = never;

export type NexusGenScalarNames = "Boolean" | "Float" | "ID" | "Int" | "String";

export type NexusGenUnionNames = never;

export interface NexusGenTypes {
  context: any;
  inputTypes: NexusGenInputs;
  rootTypes: NexusGenRootTypes;
  argTypes: NexusGenArgTypes;
  fieldTypes: NexusGenFieldTypes;
  allTypes: NexusGenAllTypes;
  inheritedFields: NexusGenInheritedFields;
  objectNames: NexusGenObjectNames;
  inputNames: NexusGenInputNames;
  enumNames: NexusGenEnumNames;
  interfaceNames: NexusGenInterfaceNames;
  scalarNames: NexusGenScalarNames;
  unionNames: NexusGenUnionNames;
  allInputTypes: NexusGenTypes['inputNames'] | NexusGenTypes['enumNames'] | NexusGenTypes['scalarNames'];
  allOutputTypes: NexusGenTypes['objectNames'] | NexusGenTypes['enumNames'] | NexusGenTypes['unionNames'] | NexusGenTypes['interfaceNames'] | NexusGenTypes['scalarNames'];
  allNamedTypes: NexusGenTypes['allInputTypes'] | NexusGenTypes['allOutputTypes']
  abstractTypes: NexusGenTypes['interfaceNames'] | NexusGenTypes['unionNames'];
  abstractResolveReturn: NexusGenAbstractResolveReturnTypes;
}

In my client I am trying to give types to the following query

{
  user {
    name
    post {
        body
    }
  }
}

the closest I can get is NexusGenFieldTypes['Query']['user'] but that returns NexusGenRootTypes['User'] which does not have a body. Is there a reason that NexusGenFieldTypes is not the following? (linking to field types instead of root types)

export interface NexusGenFieldTypes {
  Post: { // field return type
    author: NexusGenFieldTypes['User']; // User!
    body: string; // String!
  }
  Query: { // field return type
    user: NexusGenFieldTypes['User']; // User!
  }
  User: { // field return type
    name: string; // String!
    post: NexusGenFieldTypes['Post']; // Post!
  }
}
@tgriesser tgriesser added the question Further information is requested label Feb 14, 2019
@tgriesser
Copy link
Member

Yeah, so the thought here is that nexus can't know whether fields with resolvers have that actual field on the "root type" or not, for instance you could have this:

const User = objectType({
    name: 'User',
    definition: t => {
        t.string('firstName');
        t.string('lastName');
        t.string('fullName', (root) => `${root.firstName} ${root.lastName}`)
    },
});

What should the typing of root be? Nexus (rightly) assumes that it's:

{
  firstName: string
  lastName: string
}

rather than:

{
  firstName: string
  lastName: string
  fullName: string
}

because fullName does not actually exist on the root object. However nexus can only infer so much... inferring the types of the root is really meant as a backup option for types that aren't provided via the typegenAutoConfig. See the star wars or ghost for examples of that.

I have a separate ticket open to make something which would allow you to define the root types in runtime code rather than mapping them. Still need to think more about this - interested to hear feedback.

It's a good question though, and something that needs to be much better documented since the type auto generation/inference is probably 90% of what makes Nexus really powerful. I think maybe if typegenAutoConfig or similar isn't specified we should add a runtime warning with a link to documentation about how to make it work correctly.

Let me know if this makes sense or if I could clarify further.

@ecwyne
Copy link
Author

ecwyne commented Feb 14, 2019

Thank you @tgriesser. I agree that rootTypes should not include fields that have resolvers defined.

It's possible I don't understand the role of NexusGenFieldTypes. In your example fullName would not exist on rootTypes but it would exist on fieldTypes.

@tgriesser
Copy link
Member

NexusGenFieldTypes is so nexus can know what the return type/backing type of the field should be based on the GraphQL definition.

// type error on fullName because NexusGenFieldTypes['user']['fullName'] = 'string'
const User = objectType({
    name: 'User',
    definition: t => {
        t.string('firstName');
        t.string('lastName');
        t.string('fullName', (root) => 1)
    },
});

@ecwyne
Copy link
Author

ecwyne commented Feb 14, 2019

Ah, that 100% clears up what NexusGenFieldTypes is for. Thank you.

The answer may very well be that it is out of scope for this project, but what would you think of adding another NexusGen*Types that represents the types of the values returned by executing a query on the schema. I know I can use Apollo codegen or other similar tools, but it would be nice to have those types generated automatically without having to set up another step in the build process (and nexus already has all of the information available without having to run a schema introspection).

@gunar
Copy link

gunar commented Apr 10, 2019

@ecwyne I don't think that's even enough. After tinkering with the code, I was able to make it generate an exported interface called NexusGenResponseTypes. This interface is similar to NexusGenFieldTypes, but this one maps fields to NexusGenFieldTypes instead mapping them to NexusGenRootTypes, fixing our problem with nested relationships.

But that's not enough. Even with that, it is still necessary for me to declare the mapping of response type explicitly, and that's not much better than simply semi-duplicating the schema myself in the frontend.

  // Semi-duplicated schema.
  user: NexusGenFieldTypes['User']
  users: Array<NexusGenFieldTypes['User']>

  // This is not really that much better.
  user: NexusGenResponseTypes['user']
  users: NexusGenResponseTypes['users']

We want the code above to be generated for us. For that, Nexus.js would have to parse the client query, which means expanding the scope of the Nexus.js project into the front-end.

@tgriesser Would you reconsider expanding the scope of Nexus.js to the front-end as well? Or perhaps it could be developed as part of a plugin system.


For now, the proper solution is using one of these tools:

@JCMais
Copy link

JCMais commented Feb 17, 2021

@tgriesser in my plugin library I'm adding a few fields based on some definition provided by the library user: https://github.com/JCMais/nexus-plugins/blob/6150e9793a48d93ec6a2e8501019cf1306b99266/plugins/relay-global-id/src/index.ts#L115-L132

@passionkind noticed (in this PR) that those fields are not added to the root type. Is there any way for me, as a plugin author, to add some fields to the root type? In my example above, I need the source object to have an id property when the user creates a field using t.relayGlobalId('id'). Currently, this is not type-safe because the user is able to omit the id field in the source object.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested type/docs Relates to knowledge transfer matter (refs, guides, tuts, examples, ...)
Projects
None yet
Development

No branches or pull requests

4 participants