hayes / pothos

Pothos GraphQL is library for creating GraphQL schemas in typescript using a strongly typed code first approach
https://pothos-graphql.dev
ISC License
2.35k stars 163 forks source link

Cannot get PrismaTypes in the schema when running the build with npm run start (Remix.run, pothos, prisma and graphql-yoga) #1289

Open LorenzoJokhan opened 2 months ago

LorenzoJokhan commented 2 months ago

Hello,

I am having an issue with using the generated PrismaTypes in my graphql server instance using graphql-yoga. When i am running the remix application with npm run dev, then the types are set and i see my scheme:

{ schema: GraphQLSchema { ...TheUsualFields, Organisation: [GraphQLObjectType], Teacher: [GraphQLObjectType], } }

However using npm run build and npm run start results in the scheme without all my types: { schema: GraphQLSchema { ...otherFields, _typeMap: [Object: null prototype] { Boolean: [GraphQLScalarType], DateTime: [GraphQLScalarType], Float: [GraphQLScalarType], ID: [GraphQLScalarType], Int: [GraphQLScalarType], JSONObject: [GraphQLScalarType], Mutation: [GraphQLObjectType], Query: [GraphQLObjectType], String: [GraphQLScalarType], __Directive: [GraphQLObjectType], __DirectiveLocation: [GraphQLEnumType], __EnumValue: [GraphQLObjectType], __Field: [GraphQLObjectType], __InputValue: [GraphQLObjectType], __Schema: [GraphQLObjectType], __Type: [GraphQLObjectType], __TypeKind: [GraphQLEnumType] }, _subTypeMap: [Object: null prototype] {}, _implementationsMap: [Object: null prototype] {} } }

[ tsconfig.json ] { "include": [ "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/.server/**/*.ts", "**/.server/**/*.d.ts", "**/.server/**/*.tsx", "**/.client/**/*.d.ts", "**/.client/**/*.tsx" ], "exclude": ["node_modules"], "compilerOptions": { "lib": ["DOM", "DOM.Iterable", "ES2022"], "types": ["@remix-run/node", "vite/client"], "isolatedModules": true, "esModuleInterop": true, "jsx": "react-jsx", "module": "ESNext", "moduleResolution": "Bundler", "resolveJsonModule": true, "target": "ES2022", "strict": true, "allowJs": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "baseUrl": ".", "paths": { "~/": ["./app/"] },

// Vite takes care of building everything, not tsc.
"noEmit": true

} }

[vite.config] import { sentryVitePlugin } from '@sentry/vite-plugin'; import { vitePlugin as remix } from '@remix-run/dev'; import { defineConfig } from 'vite'; import { installGlobals } from '@remix-run/node'; import tsconfigPaths from 'vite-tsconfig-paths'; import { vercelPreset } from '@vercel/remix/vite';

installGlobals();

export default defineConfig(({}) => { const isProduction = process.env.NODE_ENV === 'production';

return { plugins: [ remix({ presets: [vercelPreset()], future: { v3_fetcherPersist: true, v3_relativeSplatPath: true, v3_throwAbortReason: true, }, }), tsconfigPaths(), sentryVitePlugin({ org: 'savvycodes', project: 'lessenplan-lvs', }), ],

build: {
  sourcemap: !isProduction,
},

}; });

[Graphql server] // graphql-server.js import { createYoga } from 'graphql-yoga';

import { schema } from './app/services.server/graphql/schema'; export const config = { api: { // Disable body parsing (required for file uploads) bodyParser: false, }, };

const plugins = [];

if (process.env.NODE_ENV === 'production') { // Disable introspection in production build // @todo: persisted-operations or graphql-armor would be a better solution. / eslint-disable react-hooks/rules-of-hooks / // plugins.push(useDisableIntrospection()); }

console.info({ schema });

/**

[Graphql route through remix] import { ActionFunctionArgs, json, LoaderFunctionArgs, } from '@remix-run/server-runtime'; import yoga from '../../graphql.server';

export const loader = async ({ request }: LoaderFunctionArgs) => { return yoga.handleRequest(request, { req: request, res: new Response() }); };

export const action = async ({ request }: ActionFunctionArgs) => { return yoga.handleRequest(request, { req: request, res: new Response() }); };

In development everything works great, but with a production build it does not have any of my own types. Hope that someone can push me in the right direction.

PS: When i navigate to the route /api/graphql i see the message in the editor " "message": "Type Mutation must define one or more fields." "

atman-33 commented 4 weeks ago

I encountered a similar issue.

In my case, I separated type definitions into individual files (such as post.model.ts) and imported them into an index file for the schema.

Code Before Fix

post.model.ts

import { builder } from '~/lib/graphql/builder';

export const PostStatus = builder.enumType('PostStatus', {
  values: ['DRAFT', 'PUBLIC'] as const,
});

builder.prismaObject('PostTag', {
  fields: (t) => ({
    id: t.exposeString('id'),
    post: t.relation('post'),
    tag: t.relation('tag'),
  }),
});

builder.prismaNode('Post', {
  id: { field: 'id' },
  findUnique: (id) => ({ id }),
  fields: (t) => ({
    title: t.exposeString('title'),
    emoji: t.exposeString('emoji'),
    content: t.exposeString('content'),
    status: t.expose('status', { type: PostStatus }),
    author: t.relation('author'),
    createdAt: t.expose('createdAt', { type: 'DateTime' }),
    updatedAt: t.expose('updatedAt', { type: 'DateTime' }),
    tags: t.relation('tags'),
  }),
});

post/index.ts

import './post.model';
import './post.mutation';
import './post.query';

schema/index.ts

import { builder } from '../builder';

import './post';
import './tag';
import './user';

export const schema = builder.toSchema();

In this setup, during the Vite build, the type definition files imported in schema/index.ts were being excluded from the build process.

Code After Fix

To work around this, I created a load function. Since empty functions are still omitted during the build, I added console logs to ensure the code is included.

post.model.ts

import { builder } from '~/lib/graphql/builder';

export const PostStatus = builder.enumType('PostStatus', {
  values: ['DRAFT', 'PUBLIC'] as const,
});

builder.prismaObject('PostTag', {
  fields: (t) => ({
    id: t.exposeString('id'),
    post: t.relation('post'),
    tag: t.relation('tag'),
  }),
});

builder.prismaNode('Post', {
  id: { field: 'id' },
  findUnique: (id) => ({ id }),
  fields: (t) => ({
    title: t.exposeString('title'),
    emoji: t.exposeString('emoji'),
    content: t.exposeString('content'),
    status: t.expose('status', { type: PostStatus }),
    author: t.relation('author'),
    createdAt: t.expose('createdAt', { type: 'DateTime' }),
    updatedAt: t.expose('updatedAt', { type: 'DateTime' }),
    tags: t.relation('tags'),
  }),
});

// add below
export const loadPostModel = () => {
  console.log('Post model loaded');
};

post/index.ts

import { loadPostModel } from './post.model';
import { loadPostMutation } from './post.mutation';
import { loadPostQuery } from './post.query';

export const loadPost = () => {
  loadPostModel();
  loadPostMutation();
  loadPostQuery();
};

schema/index.ts

import { builder } from '../builder';
import { loadPost } from './post';
import { loadTag } from './tag';
import { loadUser } from './user';

// load schemas
loadPost();
loadTag();
loadUser();

export const schema = builder.toSchema();