MichalLytek / typegraphql-prisma

Prisma generator to emit TypeGraphQL types and CRUD resolvers from your Prisma schema
https://prisma.typegraphql.com
MIT License
887 stars 114 forks source link

Adding custom scalars #43

Open macmillen opened 3 years ago

macmillen commented 3 years ago

It would be useful if there would be a way to map certain primitive field scalars from certain types of the prisma schema to custom graphql scalars for example with graphql-scalars to have an even stricter type system which also reduces the need for class-validator since the validation takes place directly at the resolver level. It would be really nice to have this feature to have more control over the schema generation.

Example:

type definition of Person in prisma.schema with the primitive type String:

model Person {
  email String
}

type definition of Person in schema.graphql with a custom scalar EmailAddrress:

type Person {
  email: EmailAddress
}

generated type definition of Person as a typegraphql object type with custom scalar resolver:

import { EmailAddressResolver } from 'graphql-scalars';

@ObjectType()
export class Person {
  @Field(() => EmailAddressResolver)
  email!: string;
}
MichalLytek commented 3 years ago

I'm afraid it's more complicated than it looks 😕

I wanted to do this by the custom attributes:

model User {
  /// @TypeGraphQL.scalar(name: "EmailAddress")
  email  String  @unique
}

But I realized I can only refer to built-in scalars as I don't know what import statement should it generate. For decorators it was solved by doing by TS file in runtime but TypeGraphQL has no way to alter already generated schema.

I need to find a better way to compose generation phase with TS values. Currently I'm thinking of exposing config object:

// typegraphql-config.ts
import { EmailAddressResolver } from 'graphql-scalars';

const config: TypeGraphQLGeneratorConfig = {
  scalars: {
    "EmailAddress": EmailAdressResolver,
  },
};
export default config;

Which would be then registered in generator config:

generator typegraphql {
  provider        = "node ../src/cli/dev.ts"
  output          = "../prisma/generated/type-graphql"
  config          = "../typegraphql-config"
}

So the generator would be able to generate import for that file, so the scalar can be safely referenced:

import TypeGraphQLGeneratorConfig from "../some-relative-path/typegraphql-config";

@ObjectType()
export class Person {
  @Field(() => TypeGraphQLGeneratorConfig.scalars["EmailAddress"])
  email!: string;
}

The only drawback of this approach is that the config file would need to be as much static and independent (no custom code imports) as it's possible 🤔

Secondly, it's really complicated to apply such validation or scalars to input types, based on model type metadata. DMMF doesn't provide enough info about how the types are related to each other.

I try to apply some regex-like heuristic to detect that and rename types or fields. In case of scalars, it's one level more complicated because the types can be a complex StringFilter which are harder to replace with custom scalar than simple field.

All in all, this feature might take a long time to implement, so for now I would recommend waiting a bit for applyInputTypesEnhanceMap support and just use class-validator on the fields you need 😉

macmillen commented 3 years ago

Ok that makes sense, thanks for looking into it though. 👍 I'm already looking forward to the applyInputTypesEnhanceMap.

stevefan1999-personal commented 3 years ago

@MichalLytek You can refer to unlight/prisma-nestjs-graphql, where he added options to generator field:

generator nestgraphql {
  provider = "node node_modules/prisma-nestjs-graphql"
  output   = "../src/generated/prisma/nestgraphql"

  # typescript declaration type
  types_EmailAddress_fieldType = "string"
  # graphql scalar type for @Field
  types_EmailAddress_graphqlType = "EmailAddressResolver"
  types_EmailAddress_graphqlModule = "graphql-scalars"
}

model Foo {
  /// @TypeGraphQL.scalar(name: "EmailAddress")
  email  String  @unique
}

Then it should generate:

import { EmailAddressResolver } from "graphql-scalars";

@ObjectType()
export class Foo {
  @Field(() => EmailAddressResolver)
  email!: string;
}

which should solve your problem

Also I think we can introduce default scalars in graphql-scalars freely as we should be battery packed as much as possible. This mean we could let people to opt-out using it by import_default_types = false

fivethreeo commented 3 years ago

If you could output just resolvers and a .graphql schema you could just import all types from graphql-codegen built from that .graphql. Could also output a codegen.yml with the model mappings mapped to the prisma models.

MichalLytek commented 3 years ago

@fivethreeo typegraphql-prisma emits resolver classes, not schema. TypeGraphQL can build typeDefs and resolvers but this breaks some of graphql-js features like directives and extensions, so it's not a solution to force users to do that.

carlocorradini commented 2 years ago

Any update? 😥

calvinl commented 2 years ago

Just came across this. Would be great to use custom scalars in the generated code!

sebmellen commented 1 year ago

Would love to see this get merged! It would help us a lot @cerebruminc.