Nexus-plugin-prisma: Expose input Fields

Created on 11 Feb 2019  路  16Comments  路  Source: graphql-nexus/nexus-plugin-prisma

My datamodel.prisma looks like this:

type Element {
  id: ID! @id
  name: String
  description: String
}

But I only want the field name to be exposed

const User = prismaObjectType({
  name: 'User',
  definition(t) {
    t.prismaFields(['name'])
  },
})

Problem

What I end up having in the exposed shchema is:

type Element {
  name: String!
}

type ElementCreateInput {
  name: String!
  description: String
}

Is there a way to say that I don't want the field to show up in the 'Input' types?
Because it's not only the Create type, but also Update, Upsert, Where and other types,

Thanks

Most helpful comment

@colinmcd94 I can't disclose the full code :/ but I will explain what I did, and I think it's not complicated to implement. What I I have is something like this:

const UserFields = [{"name":"name"}, {"name": "description", "readonly": true}]

Then I have a piece of code that manages the types that will be exposed on my graphql server that does something like this:

const typeFields = [];
const inputFields = [];
for (const field of fields) {
    if (!field.readonly) {
        inputFields.push(field);
    }
    typeFields.push(field);
}

const User = prismaObjectType({
    name: "User",
    definition(t) {
        t.prismaFields(typeFields);
    },
})

To set the Inputs it's a little more complex.. I get all inputs generated and for each one do the same thing with prismaObjectType

const whereInputRegexs = [
    `^UserWhereInput$`,
].map(v => new RegExp(v));
const orderByInputRegexs = [
    `^UserOrderByInput$`,
].map(v => new RegExp(v));
const insertInputRegex = [
    `^UserCreateInput$`,
    `^UserUpdateInput$`,
    `^UserCreateWithout[a-zA-Z]*Input$`,
    `^UserUpdateWithout[a-zA-Z]*DataInput$`].map(v => new RegExp(v));

There are 3 types of input generated..

InsertTypeInput: for each type found you do this

const UserCreateInput = prismaObjectType({
    name: "UserCreateInput",
    definition(t) {
        t.prismaFields(inputFields);
    },
})

OrderByInput you need to create a list based on inputFields. For each field, create 2 fields with suffix '_ASC' and '_DESC'

prismaEnumType({
            name: "User",
            members:fieldsToAdd,
        })

WhereInput you need to create a list based on inputFields. For each fields, create fields with suffix based on the type (String, number...). You can see all the possible suffix here: https://github.com/prisma/prisma/blob/214389359b94de9abec44e43110b70df46d2046a/cli/packages/prisma-generate-schema/src/generator/default/query/modelWhereInputGenerator.ts

All 16 comments

Hey, yes you can do so by doing the following:

const User = prismaObjectType({
  name: 'User',
  definition(t) {
    t.prismaFields([{ name: 'name', args: [] }]) // or alternatively args: false
  },
})

Thanks for the response.

But the field 'description' is still present on ElementCreateInput.
What's I want is to not expose it in the Type and not expose it on all inputs of User

Then you need to override the default generated ElementCreateInput by doing so:

import { prismaInputObjectType } from 'nexus-prisma'

export const ElementCreateInput = prismaInputObjectType({
  name: 'ElementCreateInput',
  definition(t) {
    t.prismaFields(['name'])
  },
})

Make sure to export it and have it in the types property of makePrismaSchema
nexus-prisma will then take your customized input object type instead of the default one

Oh, ok. Thanks :)
Do I need to do that for all 'inputs' of element? Eg: Update, Where filter..

Unfortunately, for now yes! The good news is, because nexus and nexus-prisma are "code-first", you could create your own function to automate that job if you happen to have the same use-case for other types !

Yeah, I was thinking something along those lines :)
Thanks for the help!

Awesome, closing then 馃檹

@israelglar if you end up doing this, would you be willing to post a gist of what you come up with?

@colinmcd94 I can't disclose the full code :/ but I will explain what I did, and I think it's not complicated to implement. What I I have is something like this:

const UserFields = [{"name":"name"}, {"name": "description", "readonly": true}]

Then I have a piece of code that manages the types that will be exposed on my graphql server that does something like this:

const typeFields = [];
const inputFields = [];
for (const field of fields) {
    if (!field.readonly) {
        inputFields.push(field);
    }
    typeFields.push(field);
}

const User = prismaObjectType({
    name: "User",
    definition(t) {
        t.prismaFields(typeFields);
    },
})

To set the Inputs it's a little more complex.. I get all inputs generated and for each one do the same thing with prismaObjectType

const whereInputRegexs = [
    `^UserWhereInput$`,
].map(v => new RegExp(v));
const orderByInputRegexs = [
    `^UserOrderByInput$`,
].map(v => new RegExp(v));
const insertInputRegex = [
    `^UserCreateInput$`,
    `^UserUpdateInput$`,
    `^UserCreateWithout[a-zA-Z]*Input$`,
    `^UserUpdateWithout[a-zA-Z]*DataInput$`].map(v => new RegExp(v));

There are 3 types of input generated..

InsertTypeInput: for each type found you do this

const UserCreateInput = prismaObjectType({
    name: "UserCreateInput",
    definition(t) {
        t.prismaFields(inputFields);
    },
})

OrderByInput you need to create a list based on inputFields. For each field, create 2 fields with suffix '_ASC' and '_DESC'

prismaEnumType({
            name: "User",
            members:fieldsToAdd,
        })

WhereInput you need to create a list based on inputFields. For each fields, create fields with suffix based on the type (String, number...). You can see all the possible suffix here: https://github.com/prisma/prisma/blob/214389359b94de9abec44e43110b70df46d2046a/cli/packages/prisma-generate-schema/src/generator/default/query/modelWhereInputGenerator.ts

One tip: Nexus allow types to be in any kind of shape. Which means you can have one function that returns an array of types, or an object [Type1, Type2], { Type1, Type2 } if needed! Nexus will then unwrap that

I can have something like that?:

const userFields= {
  id: {},
  description:{
    readonly: true
  },
};

Or do I need to have a name set?

const userFields= {
  id: {
    name: "id"
  },
  description:{
    name: "description"
    readonly: true
  },
};

What I mean is something like:

// InputType is a fake type name just used to illustrate that you can return an array here.
// You'd better let TS infer the type
function filterInputTypes(typeName: string, fieldsToExpose: string[]): InputType[] {
  const typesNames = getAllInputTypeNames(typeNames)

  return typesNames.map(typeName => createInputType(typeName, fieldsToExpose))
}

function createInputType(typeName: string, fieldsToExpose: string[]) {
  return prismaInputObjectType({
    name: typeName,
    definition(t) {
      fieldsToExpose.forEach((fieldToExpose) => {
        t.field(fieldToExpose, t.prismaType[fieldToExpose])
      })
    }
  })
}

Oh ok! It really is simpler :p I will try and see if that works. Thanks!

I have updated the example to show you how you could implement the fields of the input types

TS might cry though as typeName: string won't be accepted by prismaInputObjectType because it only accepts a defined union of type names. Try it by casting to any for now, we might find a better approach later if that turns out to be working fine !

I'm gonna re-open that issue as this is still interesting and might be valuable for others

Quite an old issue, closing now 馃檶

Hello! I think I have the same problem but I am not able to solve it. Also there is some wired behavior. I am trying to extend existing input type with field "OR". I was able to implement it for one type, but unfortunately was not able to do the same for the other. Here is the examples:
This code works as expected:

const ORInputType = prismaInputObjectType({
  name: 'OrderWhereInput',
  definition: (t: any) => {
    t.prismaFields(['*'])
    t.list.field("OR", {
      type: 'OrderWhereInput'
    });
  },
})

However this code:

const ORInputType = prismaInputObjectType({
  name: 'UserWhereInput',
  definition: (t: any) => {
    t.prismaFields(['*'])
    t.list.field("OR", {
      type: 'UserWhereInput'
    });
  },
})

Produces the error "UserWhereInput was already defined and imported as a type, check the docs for extending types". But actually the error should also be raised for the first case also, because both of these Input types were created by initial type generator.
So I tried to workaround the issue, as it showed me that the type is already added.

const ORInputType = prismaExtendType({
  type: 'UserWhereInput',
  definition: (t: any) => {
    t.prismaFields(['*'])
    t.list.field("OR", {
      type: 'UserWhereInput'
    });
  },
})

But unfortunately this also did not work and I got this error: ""UserWhereInput"' is not assignable to type ... (many existing types)". So it looks like it checks only for actual types and not input types.
Than I tried your last suggestion, because you wrote "TS might cry though as typeName: string won't be accepted by prismaInputObjectType because it only accepts a defined union of type names." Looks like this is my case.

const ORInputType = prismaExtendType({
  type: 'UserWhereInput' as any,
  definition: (t: any) => {
    t.prismaFields(['*'])
    t.list.field("OR", {
      type: 'UserWhereInput'
    });
  },
})

And I got this: "Must select a GraphQLObjectType, saw UserWhereInput which is UserWhereInput"

So unfortunately, I am not able to make it in any case. It would be really helpful if you could show how it can be done in a right way. Thanks!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jamesopti picture jamesopti  路  4Comments

mohe2015 picture mohe2015  路  3Comments

colinhacks picture colinhacks  路  5Comments

marticrespi picture marticrespi  路  4Comments

malekjaroslav picture malekjaroslav  路  5Comments