typegraphql-prisma icon indicating copy to clipboard operation
typegraphql-prisma copied to clipboard

Adding custom scalars

Open macmillen opened this issue 4 years ago • 8 comments

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;
}

macmillen avatar Dec 29 '20 15:12 macmillen

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 😉

MichalLytek avatar Jan 06 '21 08:01 MichalLytek

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

macmillen avatar Jan 07 '21 22:01 macmillen

@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

stevefan1999-personal avatar Feb 17 '21 17:02 stevefan1999-personal

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.

fivethreeo avatar Aug 19 '21 00:08 fivethreeo

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

MichalLytek avatar Aug 25 '21 11:08 MichalLytek

Any update? 😥

carlocorradini avatar May 09 '22 20:05 carlocorradini

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

calvinl avatar Sep 15 '22 20:09 calvinl

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

sebmellen avatar May 02 '23 19:05 sebmellen