Cauen / prisma-generator-pothos-codegen

The fastest way to create a fully customizable CRUD Graphql API from Prisma Schema.
https://www.npmjs.com/package/prisma-generator-pothos-codegen
95 stars 16 forks source link

Doc improve suggestions + error on BigInt scalar types #45

Closed masterkain closed 1 year ago

masterkain commented 1 year ago

edit: you can view all the code in this graphql branch: https://github.com/icoretech/airbroke/tree/graphql/lib/graphql

hello, I'm trying to work my way towards making this work without typescript errors but I've found some hiccups.

1) issue with importing prisma

// ./pothos.config.js

/** @type {import('prisma-generator-pothos-codegen').Config} */
module.exports = {
  inputs: {
    outputFilePath: './lib/graphql/__generated__/inputs.ts',
  },
  crud: {
    outputDir: './lib/graphql/__generated__/',
    // inputsImporter: `import * as Inputs from '@/lib/graphql/__generated__/inputs';`,
    // resolversImports: `import { prisma } from '@/lib/db';`,
    // prismaCaller: 'prisma',
  },
  global: {
    builderImporter: `import builder from '@/lib/graphql/builder';`,
  },
};

this is the config I'm playing with, let's see some generated files

// lib/graphql/__generated__/project/queries/count.base.ts
import * as Inputs from '../../inputs';
import { defineQuery, defineQueryFunction, defineQueryObject } from '../../utils';

export const countprojectQueryObject = defineQueryFunction((t) =>
  defineQueryObject({
    type: 'Int',
    nullable: false,
    args: {
      where: t.arg({ type: Inputs.projectWhereInput, required: false }),
      orderBy: t.arg({ type: [Inputs.projectOrderByWithRelationInput], required: false }),
      cursor: t.arg({ type: Inputs.projectWhereUniqueInput, required: false }),
      take: t.arg({ type: 'Int', required: false }),
      skip: t.arg({ type: 'Int', required: false }),
      distinct: t.arg({ type: [Inputs.ProjectScalarFieldEnum], required: false }),
    },
    resolve: async (_root, args, _context, _info) =>
      await _context.prisma.project.count({
        where: args.where || undefined,
        cursor: args.cursor || undefined,
        take: args.take || undefined,
        distinct: args.distinct || undefined,
        skip: args.skip || undefined,
        orderBy: args.orderBy || undefined,
      }),
  }),
);

export const countprojectQuery = defineQuery((t) => ({
  countproject: t.field(countprojectQueryObject(t)),
}));

as we can see this contains await _context.prisma.project.count({ because we did not specify prismaCaller. however if we do, and set the resolversImports it will not be imported:

import * as Inputs from '../../inputs';
import { defineQuery, defineQueryFunction, defineQueryObject } from '../../utils';

export const countprojectQueryObject = defineQueryFunction((t) =>
  defineQueryObject({
    type: 'Int',
    nullable: false,
    args: {
      where: t.arg({ type: Inputs.projectWhereInput, required: false }),
      orderBy: t.arg({ type: [Inputs.projectOrderByWithRelationInput], required: false }),
      cursor: t.arg({ type: Inputs.projectWhereUniqueInput, required: false }),
      take: t.arg({ type: 'Int', required: false }),
      skip: t.arg({ type: 'Int', required: false }),
      distinct: t.arg({ type: [Inputs.ProjectScalarFieldEnum], required: false }),
    },
    resolve: async (_root, args, _context, _info) =>
      await prisma.project.count({
        where: args.where || undefined,
        cursor: args.cursor || undefined,
        take: args.take || undefined,
        distinct: args.distinct || undefined,
        skip: args.skip || undefined,
        orderBy: args.orderBy || undefined,
      }),
  }),
);

export const countprojectQuery = defineQuery((t) => ({
  countproject: t.field(countprojectQueryObject(t)),
}));

now we have await prisma.project.count({ but no import.

not only that but from the docs https://pothos-graphql.dev/docs/plugins/prisma#set-up-the-builder

It is strongly recommended NOT to put your prisma client into Context. This will result in slower type-checking and a laggy developer experience in VSCode.

https://github.com/microsoft/TypeScript/issues/45405

I'm not familiar with any of this but the default _context.prisma goes through this process?


2) export * as Inputs

https://github.com/Cauen/prisma-generator-pothos-codegen#create-a-configuration-file-optional

this line

inputsImporter: `export * as Inputs from '@graphql/__generated__/inputs';`,

is present in the readme, I'm not sure the export is correct.


3) lib/graphql/__generated__/project/object.base.ts

import * as Inputs from '../inputs';
import {
  defineExposeObject,
  definePrismaObject,
  defineFieldObject,
  defineRelationFunction,
  defineRelationObject,
} from '../utils';

export const projectObject = definePrismaObject('project', {
  description: 'Project details along with associated notices',
  findUnique: ({ id }) => ({ id }),
  fields: (t) => ({
    id: t.exposeID('id', projectIdFieldObject),
    name: t.exposeString('name', projectNameFieldObject),
    api_key: t.exposeString('api_key', projectApi_keyFieldObject),
    organization: t.exposeString('organization', projectOrganizationFieldObject),
    repo_provider: t.exposeString('repo_provider', projectRepo_providerFieldObject),
    repo_provider_api_key: t.exposeString('repo_provider_api_key', projectRepo_provider_api_keyFieldObject),
    repo_provider_api_secret: t.exposeString('repo_provider_api_secret', projectRepo_provider_api_secretFieldObject),
    repo_branch: t.exposeString('repo_branch', projectRepo_branchFieldObject),
    repo_issue_tracker: t.exposeString('repo_issue_tracker', projectRepo_issue_trackerFieldObject),
    repo_url: t.exposeString('repo_url', projectRepo_urlFieldObject),
    notices_count: t.field(projectNotices_countFieldObject),
    created_at: t.field(projectCreated_atFieldObject),
    updated_at: t.field(projectUpdated_atFieldObject),
    notices: t.relation('notices', projectNoticesFieldObject(t)),
  }),
});

export const projectIdFieldObject = defineExposeObject('Bigint', {
  description: undefined,
  nullable: false,
});
...

here I have two typescript errors, one on t.exposeID('id' and one on defineExposeObject('Bigint':

Argument of type '"id"' is not assignable to parameter of type 'CompatibleTypes<ExtendDefaultTypes<{ PrismaTypes: PrismaTypes; Scalars: Scalars; }>, project, "ID", false> | undefined'

Argument of type '"Bigint"' is not assignable to parameter of type 'TypeParam<ExtendDefaultTypes<{ PrismaTypes: PrismaTypes; Scalars: Scalars; }>>'

I've seen something mentioned about BigInt rename in the readme, here the second warning goes away with BigInt.

this is the schema I'm working with and this is the builder

import { Prisma } from '.prisma/client';
import { prisma } from '@/lib/db';
import SchemaBuilder from '@pothos/core';
import PrismaPlugin from '@pothos/plugin-prisma';
import { Scalars } from 'prisma-generator-pothos-codegen';

import type PrismaTypes from '@/prisma/pothos-types';

const builder = new SchemaBuilder<{
  PrismaTypes: PrismaTypes,
  Scalars: Scalars;
}>({
  plugins: [PrismaPlugin],
  prisma: {
    client: prisma,
    exposeDescriptions: true,
    filterConnectionTotalCount: true,
  },
});

export default builder;

4) missing PrismaTypes in readme

const builder = new SchemaBuilder<{
  Scalars: Scalars<Prisma.Decimal, Prisma.InputJsonValue | null, Prisma.InputJsonValue>;
}>({

always in the readme we see the code above, but perhaps it should be corrected to include PrismaTypes: PrismaTypes, ?


5) JSONB

export const occurrenceBacktraceFieldObject = defineFieldObject('occurrence', {
  type: Inputs.Json,
  description: undefined,
  nullable: true,
  resolve: (parent) => parent.backtrace,
});

on parent.backtrace:

Type 'JsonValue' is not assignable to type 'MaybePromise<readonly unknown[]> | null | undefined'.
  Type 'string' is not assignable to type 'MaybePromise<readonly unknown[]> | null | undefined'.ts(2322)
builder-options.d.ts(10, 71): The expected type comes from the return type of this signature.
(property) backtrace: Prisma.JsonValue
Cauen commented 1 year ago

@masterkain Thanks for the report, i'll take a look into this

Cauen commented 1 year ago

It took me a while to look this up, thanks for your patience

1. Importing prisma I've added more details on the documentation for crud.prismaImporter option:

How to import the Prisma namespace at the objects.ts file. Default "import { Prisma } from '.prisma/client';". Please use "resolverImports" to import prismaClient at resolvers.

In your case, please use crud.resolverImports option :)

About prisma in context, I didn't know about this behavior... I'll update the example to contribute to more people using the direct import prisma.

2. Typo on docs You're correct, fixed

3. defineExposeObject error I'll look into this soon

4. Missing PrismaTypes in readme This lib was created to be integrated with the Prisma Pothos Official Plugin, but in fact this information is not obvious... So, added.

5. JSON type error Please, pass <Prisma.Decimal, Prisma.InputJsonValue, Prisma.InputJsonValue> parameters in Scalars type in the builder definition. (like in readme) I will remove the possibility of leaving these parameters invalid. Although not all users will use JSON, I think it's worth it to be safe.


I'll take a look at error 3 as soon as possible. Thank you very much for your contribution.

Cauen commented 1 year ago

Hi @masterkain I was unable to reproduce your 3 issue. ↓ image

Can you please share a minimal reproducible example?

vleandersson commented 1 year ago

@Cauen for issue 3; I've seen the same issue, and what I've found is that it seems like the defineExposeObject is used for known, primitive types like int and string. For BigInt on a normal field it seems to be using the more verbose util function defineFieldObject which works fine.

I can see this issue occuring when adding the @id attribute in the prisma schema, when a BigInt actually is generated to use the defineExposeObject util function instead of the defineFieldObject - which raises this error.

I.e. in the prisma schema id BigInt @default(autoincrement()) works fine, but id BigInt @id @default(autoincrement()) fails.

I would assume the expected behaviour is that id BigInt @id @default(autoincrement()) also generates a defineFieldObject for the field in the generated objects.base.ts file.

I'd say it's fairly common to use BigInt for id's - so this for me is a blocker for going to prod. I'll have a look if I can get started helping out a bit on the repo as I find this generation very nice 🥳 Keep up the good work! On a separate note - is there any value in making this a plugin for Pothos ? I was considering playing around and seeing if that could be neat.

Cauen commented 1 year ago

Thanks for explanation @vleandersson Fixed in 0.6.0 \o/