graphql-nexus / nexus-prisma

Prisma plugin for Nexus
https://graphql-nexus.github.io/nexus-prisma
MIT License
561 stars 45 forks source link

Generating type names end of specified suffix for avoiding conflict of name of GraphQLObjectType #115

Open oooplz opened 3 years ago

oooplz commented 3 years ago

nexus-prisma is awesome lib for me. I am hoping nexus-prisma more popular, so some helpful suggestion be written in below:

  1. Using nexus-prisma generates code of name end of specified suffix, User_、Post_、UserWhereUniqueInput_、PostWhereUniqueInput_、UserWhereInput_ and PostWhereInput_, for avoiding conflict of name of GraphQLObjectType.

  2. Providing inputObjectType and objectType Function be expose.

  3. Below code can implement namedescriptionfilterField and excludeField features etc.

How to use:

const User = objectType(User_)
const Post = objectType(Post_, {
  name: "Post",
  description: 'customDescription',
  // description: (it) => `prefix_${it}`,
  fields: [Post_.id, Post_.author]
  // fields: (it) => _.omit(it, 'id'),
  // fields: (it) => _.pick(it, 'id', 'author'),
})

const schema = makeSchema({
  types: { User, Post },
  contextType: {
    module: require.resolve('./context'),
    export: 'Context',
  },
  sourceTypes: {
    modules: [
      {
        module: '@prisma/client',
        alias: 'prisma',
      },
    ],
  },
})

Generating below code:

import { list, nonNull, nullable } from 'nexus'

export const User_ = {
  $name: 'User',
  $description: undefined,
  id: {
    name: 'id',
    type: nullable('ID'),
    description: undefined,
    resolve: undefined,
  },
  posts: {
    name: 'posts',
    type: nonNull(list(nonNull('Post'))),
    description: undefined,
    resolve: undefined,
  },
}

export const Post_ = {
  $name: 'Post',
  $description: undefined,
  id: {
    name: 'id',
    type: nullable('ID'),
    description: undefined,
    resolve: undefined,
  },
  author: {
    name: 'author',
    type: nullable('User'),
    description: undefined,
    resolve: undefined,
  },
}

export const UserWhereUniqueInput_ = {
  $name: 'UserWhereUniqueInput',
  $description: undefined,
  id: {
    name: 'id',
    type: nullable('ID'),
    description: undefined,
  },
  posts: {
    name: 'posts',
    type: nullable('PostWhereUniqueInput'),
    description: undefined,
  },
}

export const PostWhereUniqueInput_ = {
  $name: 'PostWhereUniqueInput',
  $description: undefined,
  id: {
    name: 'id',
    type: nullable('ID'),
    description: undefined,
  },
  author: {
    name: 'author',
    type: nullable('UserWhereUniqueInput'),
    description: undefined,
  },
}

// export const UserWhereInput_
// export const PostWhereInput_
// export const UserCreateInput_
// export const PostCreateInput_
// export const UserUpdateInput_
// export const PostUpdateInput_
// export const UserOrderBy_
// export const PostOrderBy_

Implementation of isInputField and inputObjectType function

import * as NexusCore from 'nexus/dist/core'
import { inputObjectType as nexusInputObjectType, objectType as nexusObjectType } from 'nexus/dist/core'

interface InputType {
  readonly $name: string
  readonly $description?: string

  readonly [key: string]: InputField | string | undefined
}

interface InputField {
  readonly name: string
  readonly type: NexusCore.NexusNullDef<any> | NexusCore.NexusNonNullDef<any> | NexusCore.NexusListDef<any>
  readonly description?: string
}

function isInputField(source: InputField | string | undefined): source is InputField {
  return typeof source === 'object'
}

export function inputObjectType(inputType: InputType) {
  return nexusInputObjectType({
    name: inputType.$name,
    description: inputType.$description,
    definition(t) {
      Object.values(inputType)
        .filter(isInputField)
        .forEach((it) => t.field(it))
    },
  })
}

export interface ObjectType {
  readonly $name: string
  readonly $description?: string

  readonly [key: string]: ObjectField | string | undefined
}

interface ObjectField {
  readonly name: string
  readonly type: NexusCore.NexusNullDef<any> | NexusCore.NexusNonNullDef<any> | NexusCore.NexusListDef<any>
  readonly description?: string
  readonly resolve: NexusCore.FieldResolver<any, any> | undefined
}

function isObjectField(source: ObjectField | string | undefined): source is ObjectField {
  return typeof source === 'object'
}

interface ObjectTypeConfig {
  readonly name?: string | ((name: string) => string)
  readonly description?: string | ((description?: string) => string)
  readonly fields?:
    | ObjectField[]
    | { string: ObjectField }
    | ((fields: { string: ObjectField }) => { string: ObjectField })
}

export function objectType(objectType: ObjectType, config?: ObjectTypeConfig) {
  let name: string
  let description: string | undefined
  let fields: ObjectField[]

  if (typeof config?.name === 'string') {
    name = config?.name
  } else if (config?.name instanceof Function) {
    name = config?.name(objectType.$name)
  } else {
    name = objectType.$name
  }

  if (typeof config?.description === 'string') {
    description = config?.description
  } else if (config?.description instanceof Function) {
    description = config?.description(objectType.$description)
  } else {
    description = objectType.$description
  }

  if (config?.fields && Array.isArray(config?.fields)) {
    fields = config?.fields
  } else if (typeof config?.fields === 'object') {
    fields = Object.values(config?.fields)
  } else if (config?.fields instanceof Function) {
    const x = Object.fromEntries(Object.entries(objectType).filter(([key, value]) => isObjectField(value))) as {
      string: ObjectField
    }
    fields = Object.values(config?.fields(x))
  } else {
    fields = Object.values(objectType).filter(isObjectField)
  }

  return nexusObjectType({
    name,
    description,
    definition(t) {
      fields.forEach((it) => t.field(it))
    },
  })
}
jasonkuhrt commented 3 years ago

Using nexus-prisma generates code of name end of specified suffix, User、Post、UserWhereUniqueInput、PostWhereUniqueInput、UserWhereInput and PostWhereInput, for avoiding conflict of name of GraphQLObjectType.

This can be achieved with namespace importing

import * as NexusPrisma from 'nexus-prisma'

const User = objectType(NexusPrisma.User)

I am open to adding an internal namespace to make auto-import trivial.

I am open to what this namespace could be called, a good name here (other than NexusPrisma) is unclear to me.

jasonkuhrt commented 3 years ago

Providing inputObjectType and objectType Function be expose.

Please open an issue on Nexus about how to support this. I think it would be less confusing if the building blocks for this were made in core. Otherwise NP will have a similar but different API to N.

oooplz commented 3 years ago

This can be achieved with namespace importing

import * as NexusPrisma from 'nexus-prisma'

const User = objectType(NexusPrisma.User)

I am open to adding an internal namespace to make auto-import trivial.

I am open to what this namespace could be called, a good name here (other than NexusPrisma) is unclear to me.

Thanks very much for your replied.

oooplz commented 3 years ago

Please open an issue on Nexus about how to support this. I think it would be less confusing if the building blocks for this were made in core. Otherwise NP will have a similar but different API to N.

It is so hard to articulate by text that we need time to tidy it -- explaining inputObjectType and objectType is how to be supported.

Thanks you again.