incetarik / nestjs-graphql-zod

A library providing dynamic GraphQL object classes from their zod validation objects.
Mozilla Public License 2.0
76 stars 11 forks source link

Is Apollo federation supported? #13

Closed railsstudent closed 1 year ago

railsstudent commented 1 year ago

I am doing a POC to perform validations on an Apollo federated graph using this library. The application has 1 Apollo gateway and 2 Apollo subgraphs, users and albums. The definition of UserModel should consist of a Directive decorator to specify the key field

@ObjectType()
@Directive('@key(fields: "id")')
export class UserModel {
  @Field(() => ID)
  id: number;

  @Field(() => String, { complexity: 3 })
  name: string;

  @Field(() => String)
  username: string;

  @Field(() => String, { complexity: 2 })
  email: string;
}

I would like to recreate UserModel based on zod. Therefore, I defined UserZod and called modelFromZod to generate ObjectType from Zod. However, I don't think Directive is created.

export const UserZod = zod.object({
  id: zod.number(),
  name: zod.string(),
  username: zod.string(),
  email: zod.string().email(),
});

const UserModel = modelFromZod(UserZod);

When I tested user query in the gateway, error occurred. The gateway could not look for albums in albums subgraph

query userById($id: Int!) {
  user (id: $id) {
    id
    email
    albums {
      id
      title
      userId
    }
  }
}
"message": "Cannot query field \"albums\" on type \"ClassFromZod_1\".",

Please advise

incetarik commented 1 year ago

Hello @railsstudent ! Thank you for your feedback.

The returned UserModel instance is a class that lets you new it if you want. This also means that you can extend another custom class from this, such as:

class CustomUserModel extends UserModel {
}

By this way, you can decorate your custom class as you wish.

Apart from this, that should give you the understanding that as the returned object is a class, you can decorate it. The ObjectType is applied inside the modelFromZod and nothing else (such as Directive), as those are irrelevant of zod validations. Therefore, what you can try is applying decorator "functions" manually like:

export const UserZod = zod.object({
  id: zod.number(),
  name: zod.string(),
  username: zod.string(),
  email: zod.string().email(),
});

const UserModel = modelFromZod(UserZod);

// See the following:
Directive('@key(fields: "id")')(UserModel)

This should work, but I'm not sure, as I have never used Directives before. If this does not work or throws an exception, then please tell me that, and I'll check that out.

Have a nice day.

incetarik commented 1 year ago

Hello again @railsstudent.

I did not see the last part as I was responding to you with an answer, but then you changed it. This library creates a model from a dynamically built class of a zod validation object. Therefore, the class will only have its validated properties, of course. As you don't have albums in the validation scheme, you will not get that validated. What you can do is extending the dynamically built class, as I have mentioned before.

To be honest, I don't know if what you have in your query is ever possible (having albums associated to a user even though you don't have it defined in the user scheme), but even if it does, you do not mention it in the zod scheme, so you won't get it. If that's possible actually, again, extend another class from this model, and it will be easier for you, maybe.

railsstudent commented 1 year ago

Yes, it works.

const ZodUserModel = modelFromZod(UserZod);

@ObjectType()
@Directive('@key(fields: "id")')
export class UserModel extends ZodUserModel {
  @Field(() => String, { complexity: 7 })
  name!: string;

  @Field(() => String, { complexity: 2 })
  email!: string;
}

Even query cost is calculated correctly