Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Getting "Schema must contain uniquely named types but contains multiple types named" for a single type #396

Closed
gotenxds opened this issue Aug 12, 2019 · 52 comments
Labels
Question ❔ Not future request, proposal or bug issue Solved ✔️ The issue has been solved

Comments

@gotenxds
Copy link

gotenxds commented Aug 12, 2019

Hello, I have the following schema:

@ObjectType()
class Action {
  @Field(type => Skill, { nullable: true })
  skill?: Skill;
}

@ObjectType()
export class ActionResult {
  @Field(type => [Action])
  actions!: Action[];
  @Field({ nullable: true })
  count?: number
}

@ObjectType()
export class Skill {}

@Resolver(Action)
export class ActionResolver  extends BaseResolver {
  @Query(returns => ActionResult)
  async actions (@Args() args: ActionArgs, @Info() info){

  }

  @FieldResolver()
  async skill(@Root() action: Action) {
  }
}

@Resolver(Skill)
export class SkillResolver extends BaseResolver {
  @Query(returns => [Skill])
  async allSkills (){

  }
}

From this I generate a schema like this:

  return buildSchema({
    resolvers: [ActionResolver, SkillResolver],
    emitSchemaFile: { path: 'schema.graphql' },
  })

And I get the error:

UnhandledPromiseRejectionWarning: Error: Schema must contain uniquely named types but contains multiple types named "Skill".
@MichalLytek
Copy link
Owner

You can't extend a resolver's class without providing different queries/mutation names in a factory function or without making the base resolver isAbstract. This will result in duplicated queries/mutations in schema.

@MichalLytek MichalLytek added the Question ❔ Not future request, proposal or bug issue label Aug 12, 2019
@gotenxds
Copy link
Author

gotenxds commented Aug 12, 2019

Hi, not sure what you mean, the BaseResolver class looks like this :

export default abstract class DefaultResolver {
  async fetch(args, info) {
   //logic
}
  async fetchOne(id: string) {
    return this.model.findOne({ _id: id });
  }

  async fetchAll() {
      //logic
  }

It does not have the @Resolver
How is this not ok ?

@MichalLytek
Copy link
Owner

Oh, I see it's Skill type, not skill field from field resolver.
So please create a repository with a minimal reproducible code example.

BTW, it's better to inject a base service rather that extending a class to have access to common methods.

@gotenxds
Copy link
Author

I would rather inject common methods, but then I would have to send the name of the model as a parameter, right now my BaseResolver has acess to the names of the model:

export default abstract class DefaultResolver {
  abstract get name (): string;
  abstract get plural (): string;

  protected get model() {
    return models[this.name];
  }
  async fetch(args, info) {
      const {filter} = args;
      const shouldCount = info.fieldNodes[0].selectionSet.selections.map(s => s.name.value).includes('count');
      const filters = filterPipe(filter);

      const data = await this.model.find(filters, null, skipLimitPipe(args));
      const count = shouldCount ? await countModel(this.model, filters) : -1;

      return {[this.plural]: data, count};
  }

  async fetchAll() {
    return this.model.find();
  }

  async fetchOne(id: string) {
    return this.model.findOne({ _id: id });
  }
}

@MichalLytek MichalLytek added the Need More Info 🤷‍♂️ Further information is requested label Aug 12, 2019
@AdamDanielKing
Copy link

I found one way to produce this bug. Took me a long time to figure out! If you import a file from 2 other files but you type its path with 2 different casings, the TypeScript compiler will include 2 copies of the module in the transpiled JS. This causes any types to be registered multiple times. For example:

// recipe.ts
@ObjectType()
export class Recipe {
// ...
}
// file1.ts
import { Recipe } from './recipe';
console.log(Recipe); // Use it so the import doesn't get optimized away.
// file2.ts
import { Recipe } from './Recipe'; // Notice the capital R
console.log(Recipe); // Use it again.

Error: Schema must contain uniquely named types but contains multiple types named "Recipe".

Of course, the import statements should match the casing of the actual file. But the macOS filesystem is not case sensitive so it recognizes the file even when the import statement has the wrong casing. There is the compiler option forceConsistentCasingInFileNames that detects bugs like this. Strangely it's turned off by default.

@gotenxds Was this also the cause in your case?

@MichalLytek
Copy link
Owner

Closing for a housekeeping purposes 🔒

@MichalLytek MichalLytek added Solved ✔️ The issue has been solved and removed Need More Info 🤷‍♂️ Further information is requested labels Aug 23, 2019
@jonlambert
Copy link

jonlambert commented Aug 26, 2019

I'm struggling with this issue too. Would love to know how you solved it. Unfortunately @AdamDanielKing's solution didn't work, all import statements have consistent casing 😬

@AdamDanielKing
Copy link

@jonlambert Would you be able to put together a minimal project that demonstrates the issue, so others can take a look?

@jonlambert
Copy link

Sure, I'll strip back the project I'm working on as soon as possible. It's a NestJS project, so let me know if you think the issue might be better raised there.

@jonlambert
Copy link

This was a hard one to track down, with an annoying fix (Nest specific, not the fault of type-graphql). For anyone experiencing the same issue make sure to delete dist/ directory and run the app again.

@christopher-avila
Copy link

I have the same issue and I have it very bounded:
Object Type definition:

import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from 'typeorm';
import { ObjectType, Field, ID } from 'type-graphql';

@ObjectType()
@Entity()
export class Point extends BaseEntity {
    @Field(() => ID)
    @PrimaryGeneratedColumn()
    id!: number;

    @Field()
    @Column()
    idMap!: number;
}

Resolver definition:

import { Point } from '../entities/Point';
import { Resolver, Query, Arg } from 'type-graphql';
import { Repository, getManager } from 'typeorm';

@Resolver()
export class PointResolver {
    private pointService: Repository<Point>;
    constructor() {
        this.pointService = getManager().getRepository('Point');
    }

    @Query(() => [Point])
    async pointsByIdMap(@Arg('idMap') idMap: number) {
        const points = this.pointService.find({ where: { idMap } });

        if (!points) {
            return null;
        }

        return points;
    }
}

So all of it works perfectly until I add the () => [Point] to the query. If instead of returning an array of Point, we return an array of Number for example (return [1,2]), it works fine. So maybe the call to the () => [Point] is interfering somehow?

The main tutorial I've been following to get to this issue is https://github.com/benawad/type-graphql-series but I don't find any reason for this problem.

Any idea?

@AdamDanielKing
Copy link

I think you have a similar problem to mine and @jonlambert's. The reason it manifests when you add () => [Point] is that otherwise you're not using the Point import so TypeScript removes it as an optimization. I suspect you would also get the error if you simply added a console.log(Point).

Your code runs for me. Are you sure there isn't another file where you're importing Point with different casing in the filename, e.g. import { Point } from '../entities/point';?

@christopher-avila
Copy link

christopher-avila commented Oct 1, 2019

I only have another file related with this, an InputType definition but I never use it since I'm trying to simplify the debugging:

import { Field, InputType } from 'type-graphql';

@InputType()
export class PointInput {
    @Field()
    idMap!: number;
}

When I search for any import or any .../entities/ in the project, only appear one result, the import in the resolver.

EDIT:
You were right, if I create another Query called version, and try to console.log(Point), throw the same error.

@christopher-avila
Copy link

christopher-avila commented Oct 2, 2019

Here is the tests I've done:

  1. When I launch the project with ts-node src/index.ts which is the main file, it works perfectly:
query: SELECT * FROM "information_schema"."tables" WHERE "table_schema" = current_schema() AND "table_name" = 'typeorm_metadata'
query: COMMIT
server started on http://localhost:4000/graphql
  1. When I compile the TypeScript and try to execute the code with tsc-watch --onSuccess \"ts-node -r dotenv/config dist/index.js dotenv_config_path=./.env\" the same error appears again.

  2. Also appears with ts-node /dist/index.js. So looks like is the compilation process which is interfering somehow.

Any idea with this new info? @MichalLytek @AdamDanielKing

@AdamDanielKing
Copy link

Not sure. Would you be able to publish a minimal repo that reproduces the error?

@christopher-avila
Copy link

Sure. I'm on it.

@christopher-avila
Copy link

christopher-avila commented Oct 2, 2019

@AdamDanielKing
Copy link

@christopher-avila Your ormconfig.js is referencing the TS file for your entity:

https://github.com/christopher-avila/type-graphql-and-typeorm-debug/blob/a45b075dd3e03e06aedef0cf5dec31ac91241680/ormconfig.js#L12

You should either configure typeorm from a TS file or use the "dist/" path to the JS files:

entities: ["dist/entities/*.js"]

This fixes the problem. 🙂

@christopher-avila
Copy link

I can't believe it.

The simplest thing in the simplest file in the whole project.

Thank you so much for your time. Solved, right! Thank you!

@reggi
Copy link

reggi commented Nov 24, 2019

Thank you @AdamDanielKing I have been struggling with a casing mismatch for the past couple of hours. Fixed the issue I was having.

@vlad-elagin
Copy link

For anyone who is struggling with this issue and tried above recipes with no luck: try to implicitly give a name to your types. My issue was solved with following

@ObjectType('user')
export class User {

Note an arg that was passed to ObjectType

@fromi
Copy link

fromi commented Dec 11, 2019

For the record, I have this issue using Zeit now CLI tool. My GraphQL server is a lambda. First type it compiles, it works fine. When I change something in the code, it recompiles, and produce the error (Schema must contain uniquely named types but contains multiple types named...)
I'm still struggling to find a solution.

@fromi
Copy link

fromi commented Dec 11, 2019

Looks like this is related with Webpack hot reload compatibility: #289

@MichalLytek
Copy link
Owner

@fromi
Do you use type-graphql@beta? It has fixed schema types leaks so now it should built the schema correctly, although it will still evaluate all decorators so there will be a memory leak on metadata storage side.

@fromi
Copy link

fromi commented Dec 11, 2019

@MichalLytek I just tried, and it fixes my issue! Thanks 👍

@hitallow
Copy link

@vlad-elagin thank you a lot

@THPubs
Copy link

THPubs commented Jan 24, 2020

@MichalLytek I just tried the beta and it worked. But how stable is it? Do you recommend using it?

@MichalLytek
Copy link
Owner

@THPubs it's basically a RC, I was grouping breaking changes, soon v0.18 stable will be released 😉

@THPubs
Copy link

THPubs commented Jan 24, 2020

@MichalLytek Cool thanks 😀

@rwieruch
Copy link

rwieruch commented Apr 9, 2020

@fromi
Do you use type-graphql@beta? It has fixed schema types leaks so now it should built the schema correctly, although it will still evaluate all decorators so there will be a memory leak on metadata storage side.

This solved it for me as well @MichalLytek FYI I am using Next.js which comes with hot reloading out of the box. The initial GraphQL query was always fine, but every other query resulted in this error too.

@rwieruch
Copy link

rwieruch commented Apr 15, 2020

FYI: Whenever I used "DateTime" as input args, I got Cannot find module 'class-validator' Require stack for type-graphql@beta. I installed class-validator manually, then it worked, but then I got No metadata found. There is more than once class-validator version installed probably. You need to flatten your dependencies.. Not sure whether there is something missing in the latest beta release.

@MichalLytek
Copy link
Owner

@rwieruch
class-validator is now a peer dependency, so it will use the same version as your app.
If you don't use the validation feature, just put validate: false in the buildSchema options.

@sandipndev
Copy link

Was using webpack to build, getting the same error on the generated bundle.
Fixed by putting ".mjs" in the extensions of webpack.config.js

{
    ...,
    resolve: {
        extensions: [".mjs", ".ts", ".tsx", ".js"]
    }
}

No idea how mjs helps in this though.

@bipinswarnkar1989
Copy link

For anyone who is struggling with this issue and tried above recipes with no luck: try to implicitly give a name to your types. My issue was solved with following

@ObjectType('user')
export class User {

Note an arg that was passed to ObjectType

yeah this is the real solution.

@fider-apptimia
Copy link

Complete solution in case if class is used as input and output:

@ObjectType('DataStruct')
@InputType('DataStructInput')
export class DataStruct { ... }

@himanshu199728
Copy link

In my case I have created result model with decorater @ObjectType("result") for all models so due to conflit in different result model for each schema/resolver type-graphql schema generation pipeline giving error. So make sure fields, @ObjectType(), @inputType() should be unique

@miodrage
Copy link

miodrage commented Nov 7, 2020

I fixed the issue by changing the paths.

import { User } from "./user.entity"; => import { User } from "../User/user.entity";

@mahuzedada
Copy link

mahuzedada commented Jan 7, 2021

I had the same issue because I decorated the same class as @ObjectType and @InputType

@ObjectType()
@InputType()
export class ProductDto {
  @Field() readonly id?: number;
  @Field() readonly name: string;
}

The solution was to explicitly name the objects

@ObjectType('ProductDto')
@InputType('ProductDtoInput')
export class ProductDto {
  @Field() readonly id?: number;
  @Field() readonly name: string;
}

@omar-dulaimi
Copy link

I'm getting this variation after deployment to Aws lambda:

{
    "errorType": "Error",
    "errorMessage": "Schema must contain uniquely named types but contains multiple types named \"h\".",
    "stack": [
        "Error: Schema must contain uniquely named types but contains multiple types named \"h\".",
        "    at new GraphQLSchema (/var/task/node_modules/graphql/type/schema.js:194:15)",
        "    at Function.generateFromMetadataSync (/var/task/node_modules/type-graphql/dist/schema/schema-generator.js:31:32)",
        "    at Object.buildSchemaSync (/var/task/node_modules/type-graphql/dist/utils/buildSchema.js:20:55)",
        "    at Object.t.default (/var/task/src/index.js:1:5251373)",
        "    at Object.80341 (/var/task/src/index.js:1:5271735)",
        "    at n (/var/task/src/index.js:1:5439183)",
        "    at /var/task/src/index.js:1:5439223",
        "    at Object.<anonymous> (/var/task/src/index.js:1:5439330)",
        "    at Module._compile (internal/modules/cjs/loader.js:999:30)",
        "    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)"
    ]
}

I'm not sure where to start looking. Any hints?

@conoremclaughlin
Copy link

conoremclaughlin commented May 18, 2021

I'm getting this variation after deployment to Aws lambda:

{
    "errorType": "Error",
    "errorMessage": "Schema must contain uniquely named types but contains multiple types named \"h\".",
    "stack": [
        "Error: Schema must contain uniquely named types but contains multiple types named \"h\".",
        "    at new GraphQLSchema (/var/task/node_modules/graphql/type/schema.js:194:15)",
        "    at Function.generateFromMetadataSync (/var/task/node_modules/type-graphql/dist/schema/schema-generator.js:31:32)",
        "    at Object.buildSchemaSync (/var/task/node_modules/type-graphql/dist/utils/buildSchema.js:20:55)",
        "    at Object.t.default (/var/task/src/index.js:1:5251373)",
        "    at Object.80341 (/var/task/src/index.js:1:5271735)",
        "    at n (/var/task/src/index.js:1:5439183)",
        "    at /var/task/src/index.js:1:5439223",
        "    at Object.<anonymous> (/var/task/src/index.js:1:5439330)",
        "    at Module._compile (internal/modules/cjs/loader.js:999:30)",
        "    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)"
    ]
}

I'm not sure where to start looking. Any hints?

From the look of it and for anyone else that comes along, your files are being minified and your class names are being minified to the same character. This then causes a conflict when TypegraphQl tries to infer the name of the graphql type. Adding the name as an argument to ObjectType or InputType as noted above should fix your issue:

@ObjectType('Pagination')
export class Pagination {
  @Field(() => Number)
  total: number;

  @Field(() => Boolean, { nullable: true })
  hasMore?: boolean;
}

@txd22081999
Copy link

Thanks @vlad-elagin, this work for my NestJS project

@biniyammoges
Copy link

I can't believe it.

The simplest thing in the simplest file in the whole project.

Thank you so much for your time. Solved, right! Thank you!

How did you fix it? I tried all solution, none of them worked for me

@nglgzz
Copy link

nglgzz commented Sep 2, 2022

Another way to fix this, if the issue is the type names getting minified is to disable the minification in Webpack. Had the same issue with Nestjs and fixed by adding the following to my Webpack config:

optimization: {
  minimize: false,
},

https://webpack.js.org/configuration/optimization/#optimizationminimize

@srosato
Copy link

srosato commented Oct 13, 2022

I have the same problem with NextJs hot reload.

In my case the error is Schema must contain uniquely named types but contains multiple types named "HoursWorked"

I have a file called object-types and I put my custom types in there:

import { Field, Float, GraphQLISODateTime, Int, ObjectType } from 'type-graphql';
import { WorkShift } from '@generated/type-graphql';

@ObjectType()
export class HoursWorked {
  @Field(() => Float)
  duration: number;

  @Field(() => Float)
  breakDuration: number;

  @Field(() => Float)
  regularHours: number;

  @Field(() => Float)
  doubletimeHours: number;

  @Field(() => Float)
  nightHours: number;

  @Field(() => Float)
  overtimeHours: number;
}

@ObjectType()
export class Timesheet {
  @Field(() => HoursWorked)
  hoursWorked: HoursWorked;

  @Field(() => [WorkShift])
  workShifts: WorkShift[];
}

@ObjectType()
export class WorkWeek {
  @Field(() => GraphQLISODateTime)
  startOfWeek: Date;

  @Field(() => GraphQLISODateTime)
  endOfWeek: Date;

  @Field(() => Int)
  year: number;

  @Field(() => Int)
  weekOfYear: number;
}

The only way I am able to remove the error is by putting the schema as global. But then I lose the ability to actually hot reload when I change resolvers...

schema.ts

import { buildSchema, MiddlewareFn } from 'type-graphql';
import { authChecker } from '@ss/auth';
import { GraphQLYogaError } from 'graphql-yoga';
import { GraphQLSchema } from 'graphql';
import { isDev, isProd } from '@ss/environment';
import { resolvers } from '..';

export const ErrorInterceptor: MiddlewareFn<unknown> = async (_, next) => {
  try {
    return await next();
  } catch (err) {
    throw new GraphQLYogaError(err as unknown as string);
  }
};

declare global {
  // eslint-disable-next-line no-var
  var graphQLSchema: GraphQLSchema | undefined;
}

export const createSchema = async () => {
  if (global.graphQLSchema) {
    return global.graphQLSchema;
  }

  const schema = await buildSchema({
    resolvers: [...resolvers],
    authChecker,
    validate: false,
    emitSchemaFile: isDev(),
    dateScalarMode: 'isoDate',
    globalMiddlewares: [ErrorInterceptor]
  });

  if (!isProd()) {
    global.graphQLSchema = schema;
  }

  return schema;
};

I have been at it for countless hours it is driving me insane!

@paulmil1
Copy link

paulmil1 commented Jan 24, 2023

@ObjectType()
export class HoursWorked {

change to

@ObjectType("hoursWorked")
export class HoursWorked {

and potentially disable webpack minimization

  optimization: {
        minimize: false,
    },

@hainessss
Copy link

why is this closed? running into this error is a complete blocker. I am also hitting when using nextJS. Seems to only be a problem with hot reload. What is the fix?

this has not effect.

@ObjectType("hoursWorked")
export class HoursWorked {

minimize is false by default in development.

@valerii15298
Copy link

Having the same issue when using multiple prisma schemas:
Schema must contain uniquely named types but contains multiple types named \"NullableStringFieldUpdateOperationsInput\"

@hainessss
Copy link

hainessss commented May 3, 2023

using the global trick

  if (global.graphQLSchema) {
   return global.graphQLSchema;
 }

that @srosato mentioned above will fix the problem @valerii15298.

however this breaks hot reloading in nextjs, which is a huge hit to DX. is there a better way? @MichalLytek

@GeekEast
Copy link

For my case

Don't put multiple ObjectType into a single ts file

@kisstamasj
Copy link

kisstamasj commented May 6, 2024

This was a hard one to track down, with an annoying fix (Nest specific, not the fault of type-graphql). For anyone experiencing the same issue make sure to delete dist/ directory and run the app again.

This worked for me.
I'm facing with the issue only when I run my e2e tests in nestjs using jest. I tried to ignore the dist forlder with different config settings, but still only works when I delete the dist folder. Interesting..

@zyf0330
Copy link

zyf0330 commented Jun 28, 2024

In my situation, I change a ts file name, and don't clean dist before another tsc build, so there are two same type in these two files with different filename in dist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question ❔ Not future request, proposal or bug issue Solved ✔️ The issue has been solved
Projects
None yet
Development

No branches or pull requests