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

Create factories for Entities, Types, resolvers: Cannot determine GraphQL output type for getAllItems #342

Closed
jeromeSH26 opened this issue May 25, 2019 · 4 comments
Labels
Question ❔ Not future request, proposal or bug issue Solved ✔️ The issue has been solved

Comments

@jeromeSH26
Copy link

Hi,

I'm using TypeORM together with type-graphql. I would like to buid a factory that build boths entities and grapqhQL schema, based on an interface. The main requirement behind is to create entites and resolvers to manage google apis that have sometimes more than 50 fields. I would like to avoid re-listing all api schemas. So I'm trying to build a factory.

I'm trying to do this by achieving 4 steps:
1- converting the interface to a class (generic factory convert function)
2- using this class to create the entity (still generic)
3- using this class to create the @ObjectType and @inputType
4- creating the resolver. (generic resolver)

Unfortunately, when building the schema, I end up with an error " Cannot determine GraphQL output type for getAllItems

I know that the issue is not in the resolver by itself, as if I create a simple class with @ObjectType, @inputType along with the entity, it works fine.

The problem are the steps before, i.e. when trying to create the Entity with a generic and abstract class or the ObjectType, InputType also using an abstract class.

I'm struggling with this for a while, any help would be appreciated.

Below is the factory part, as well as a very simple example that fails.

Factory code

import {
  Arg,
  ClassType,
  Field,
  ID,
  InputType,
  MiddlewareFn,
  Mutation,
  ObjectType,
  Query,
  Resolver,
  UseMiddleware
} from "type-graphql";
import { Column, Entity, PrimaryColumn } from "typeorm";

// Convert GoogleSchema (interface) to Class
function BaseClass<T extends object>() {
  return (function(this: T, data: T) {
    Object.assign(this, data);
  } as any) as new (arg: T) => T;
}

// create a entity based on class
export function GoogleApiSchemaToEntity<TGSchema>(
  entityName: string,
  dbSchema: string
) {
  @Entity({
    name: entityName,
    schema: dbSchema
  })
  abstract class GoogleApiGenericEntity {
    @PrimaryColumn({
      type: "character varying",
      length: 255,
      nullable: false,
      unique: true
    })
    id: string;
    @Column()
    schema: TGSchema;
  }
  return GoogleApiGenericEntity;
}

// convert Google Schema to TypeGraphQL type definituin
export default function GoogleApiSchemaToClass<TGSchema>(
  TGSchemaClass: ClassType<TGSchema>
) {
  @ObjectType({ isAbstract: true })
  abstract class GoogleApiGenericClass {
    @Field(() => ID)
    id: string;
    @Field(() => [TGSchemaClass])
    schema: TGSchema[];
  }
  return GoogleApiGenericClass;
}

// generic resolver
function createGoogleApiResolvers<T extends ClassType, X extends ClassType>(
  suffix: string,
  outputType: T,
  inputType: X,
  entity: any,
  middleware?: Array<MiddlewareFn<any>>
) {
  @Resolver()
  class GoogleApiBaseResolvers {
    // get all items of entity
    @UseMiddleware(...(middleware || []))
    @Query(() => [outputType!]!, {
      nullable: true,
      name: `getAll${suffix}`
    })
    async getAllItems(): Promise<T[] | null> {
      let items: T[] | null = null;
      items = await entity.find();
      return items;
    }
    // update an item
    @UseMiddleware(...(middleware || []))
    @Mutation(() => outputType!, { nullable: true, name: `updateOne${suffix}` })
    async updateOneItem(
      @Arg("data", () => inputType) data: X
    ): Promise<T | null> {
      let item: T | null = new entity();
      let val: any = "";
      if (!data.hasOwnProperty("id")) {
        return null;
      }
      val = data.hasOwnProperty("id");

      item = (await entity.findOne({ id: val })) || null;

      if (!item) {
        return null;
      }
      await entity.update({ id: val }, { data });
      return item;
    }
  }
  return GoogleApiBaseResolvers;
}

Simple test to call the factory

// test :
interface test {
  field1: string;
  filed2: string;
}

// create entity base on test  schema
class GOrgUnitEntity extends GoogleApiSchemaToEntity<test>("test", "public") {}

// Convert Schema$Orgunit to Class
class GOrgUnitsClass extends BaseClass<test>() {}

// create object type
@ObjectType()
export class GoogleOrganizationUnitsOutputType extends GoogleApiSchemaToClass<
  GOrgUnitsClass
>(GOrgUnitsClass) {
  @Field(() => [String])
  otherInfo: string[];
}

@InputType()
export class GoogleOrganizationUnitsInputType extends GoogleApiSchemaToClass<
  GOrgUnitsClass
>(GOrgUnitsClass) {
  @Field(() => [String])
  otherInfo: string[];
}

export const GOrgUnitsResolver = createGoogleApiResolvers(
  "GOrgUnits",
  GoogleOrganizationUnitsOutputType,
  GoogleOrganizationUnitsInputType,
  GOrgUnitEntity,
  []
);

// schema buidling : error is here
// Cannot determine GraphQL output type for getAllItems

const initApolloServer = async (): Promise<ApolloServer> => {
  try {
    const schema: GraphQLSchema = await buildSchema({
      resolvers: [
        GOrgUnitsResolver
      ]
    });
    const apolloServer: ApolloServer = new ApolloServer({
      schema,
      context: ({ req, res, info }: any) => ({ req, res, info }),
      formatError: (err: GraphQLError) => FormatErrorMessageGraphQlServer(err)
    });

@MichalLytek MichalLytek added Need More Info 🤷‍♂️ Further information is requested Question ❔ Not future request, proposal or bug issue labels May 25, 2019
@MichalLytek
Copy link
Owner

MichalLytek commented May 25, 2019

@jeromeSH26

I end up with an error "Cannot determine GraphQL output type for getAllItems

When I run this code, I get "Error: Cannot determine GraphQL output type for schema"

Looks like GOrgUnitsClass used in GoogleApiSchemaToClass<GOrgUnitsClass>(GOrgUnitsClass) has to be decorated with @ObjectType() and @Fields() decorators.

Otherwise, use GraphQL JSON scalar and loose type safety.

@jeromeSH26
Copy link
Author

jeromeSH26 commented May 25, 2019

"Looks like GOrgUnitsClass used in GoogleApiSchemaToClass(GOrgUnitsClass) has to be decorated with @ObjectType() and @fields() decorators."

By doing this, if I understand well, it means I had to retype all the interface definition, and add @field to the GOrgUnitsClass ?
This what I'd like to avoid. Is there a way to avoid this ?

And yes, sometimes the error is the one you mentionned, or the one I have reported. That's why I'm lost ;-)

@MichalLytek
Copy link
Owner

MichalLytek commented May 25, 2019

Is there a way to avoid this ?

How Node can figure out in runtime which data you will return without providing that info? Magic? 😄

All you can do is to use JSON scalar that accept everything but no type info on the client side:
https://github.com/taion/graphql-type-json

@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 Jun 23, 2019
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

2 participants