TypeGraphql Field and Arg decorators using custom type - graphql

I'm trying to build a resolver using type-graphql library and found that I can't define custom argument type. Here is my code:
type Hits = { [K: string]: string | number }
#Resolver()
export default class SearchResolver {
#Query(() => [String], { nullable: true })
#UseMiddleware(isAuthenticated)
async searchAssetList(#Arg('hits') hits: Hits) {
return [];
}
}
I got an error:
NoExplicitTypeError: Unable to infer GraphQL type from TypeScript reflection system. You need to provide explicit type for argument named 'hits' of 'searchAssetList' of 'SearchResolver' class.
I also tried to define an input class:
type Hits = { [K: string]: string | number }
#ObjectType()
class SearchListInput {
#Field(() => GraphQLObjectType)
hits: Hits;
}
#Resolver()
export default class SearchResolver {
#Query(() => [String], { nullable: true })
async searchAssetList(#Arg('input') input: SearchListInput) {
return []
}
}
and got another error:
UnhandledPromiseRejectionWarning: Error: Cannot determine GraphQL input type for argument named 'input' of 'searchAssetList' of 'SearchResolver' class. Is the value, that is used as its TS type or explicit type, decorated with a proper decorator or is it a proper input value?
Replacing #ObjectType with #InputType also doesn't help. How to define decorators like #Field, #Arg correctly?
Any help is appreciated. Thanks.

I think you are trying to have an Arg of an array whit numbers and strings in that case you should do
#InputType()
class HitsInput
{
#Field()
Hits: [number | string];
}
#Resolver()
export default class SearchResolver {
#Query(() => [String], { nullable: true })
async searchAssetList(#Arg('input') input: HitsInput) {
return []
}
}
if this is not the case and you want an object whit dynamic fields you need to define the object, let say if hits have Name and Id_Hits,
#InputType()
class HitsInput
{
#Field()
Id_Hits: number;
#Field()
Name: string;
}
#Resolver()
export default class SearchResolver {
#Query(() => [String], { nullable: true })
async searchAssetList(#Arg('input') input: HitsInput) {
return []
}
}
and if you want to have a dynamic args base in the user request I am not quite sure that it is possible, but it is possible to do this
#InputType()
class HitsInput
{
[key: string]: any;
}

I had exactly the same issue, a couple of days ago, and I solve it using GraphQLScalarType by creating my own custom type
here how to do it
import { GraphQLScalarType} from "graphql";
export const GraphQLAny = new GraphQLScalarType({
name: 'Any',
serialize: (value) => value,
parseValue: (value) => value,
parseLiteral: (ast) => ast
});
and in your class you can use your customTpe like this:
#ObjectType()
class SearchListInput {
#Field(() => GraphQLAny)
hits: typeof GraphQLAny;
}

Related

How to use class-validator validate Date is older than now and unique key in an array?

Using class-validator with Nest.js. I want to validate these two cases:
Validate the input date is older than now, then give a message: Date can't before than now.
#Field(() => Date, { description: 'Due Date' })
dueDate: Date;
Validate if all of the keys are unique in an array. But this way only can check if the ID is uuid. Is it possible to check if the IDs are the same in the array? Ex: ['1234-1234-1234-1234', '1234-1234-1234-1234']
#Field(() => [String], { description: 'product IDs' })
#IsUUID('all', { each: true, message: 'Product ID is not valid.' })
productIds: string[];
I searched and couldn't find a suitable inheriting validation decorators. You can custom validation classes like this:
#ValidatorConstraint()
export class IsAfterNowConstraint implements ValidatorConstraintInterface {
validate(date: Date) {
return Date.now() < date.getTime();
}
defaultMessage(args: ValidationArguments) {
return `Date ${args.property} can not before now.`;
}
}
function IsAfterNow(validationOptions?: ValidationOptions) {
// eslint-disable-next-line #typescript-eslint/ban-types
return function (object: Object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: IsAfterNowConstraint,
});
};
}
#ArrayUnique(identifier?: (o) => any): Checks if all array's values are unique. Comparison for objects is reference-based. Optional function can be speciefied which return value will be used for the comparsion.
You can compare two dates with this function
import { ValidateBy, ValidationOptions, buildMessage, ValidationArguments } from 'class-validator'
export const IsAfter = (property: string, options?: ValidationOptions): PropertyDecorator =>
ValidateBy(
{
name: 'IsAfter',
constraints: [property],
validator: {
validate: (value: Date, args: ValidationArguments): boolean => {
const [relatedPropertyName] = args.constraints
const relatedValue = (args.object as Record<string, unknown>)[relatedPropertyName] as Date
return value.toISOString() > relatedValue.toISOString()
},
defaultMessage: buildMessage((each: string): string => each + '$property must be after $constraint1', options),
},
},
options,
)
in this kind of situations
#IsDate()
#Type(() => Date)
#IsNotEmpty()
readonly from!: Date
#IsAfter('from')
#IsDate()
#Type(() => Date)
#IsNotEmpty()
readonly to!: Date

Get ManyToOne field using TypeORM and Graph QL query

I have a problem with a TypeORM entity from my project.
The code for entity is shown below.
import { Field, ObjectType } from "type-graphql";
import { BaseEntity, Column, Entity, ManyToOne, PrimaryColumn, PrimaryGeneratedColumn } from "typeorm";
import { MusicProvider } from "./MusicProvider";
import { User } from "./User";
#ObjectType()
#Entity()
export class UserMusicProvider extends BaseEntity {
#Field()
#PrimaryGeneratedColumn()
id!: number;
#Field(() => User)
#ManyToOne(() => User, (user) => user.id, { onDelete: 'CASCADE', cascade: true })
user: User
#PrimaryColumn()
userId: number
#Field(() => MusicProvider)
#ManyToOne(() => MusicProvider, (musicProvider) => musicProvider.id, { onDelete: 'CASCADE', cascade: true })
musicProvider: MusicProvider
#Column()
musicProviderId: number
#Field()
#Column()
musicProviderUserId: string;
}
As you can see, it has a field for MusicProvider. Into my GraphQL query I want to be able to get the MusicProvider's data, because I want to show this data into my app. I want to do this whitin this query, not using two queries.
The problem is that when I call this query:
query{
getUserMusicProviders(userId: 27){
userMusicProviders{
id
musicProviderUserId
musicProvider{
id
name
}
}
errors{
field
message
}
}
}
i receive an error saying:
Cannot return null for non-nullable field
UserMusicProvider.musicProvider.
which is true, because, as I checked, the MusicProvider returned is undefined. I don't know why this happens and would be really helpful to solve this issue.
The code for my query is this:
#Query(() => UserMusicProvidersResponse)
async getUserMusicProviders(
#Arg("userId") userId: number
): Promise<UserMusicProvidersResponse | null> {
const resp = await UserMusicProvider.find({ where: { userId } })
return {
userMusicProviders: resp
}
}
and the code for the UserMusicProvidersResponse is:
#ObjectType()
export class UserMusicProvidersResponse {
#Field(() => [FieldError], { nullable: true })
errors?: FieldError[];
#Field(() => [UserMusicProvider], { nullable: true })
userMusicProviders?: UserMusicProvider[]
}
Thank you for your time and thank you for trying to help me. If you want to clarify something, feel free to send a comment.

Resolve field returns null for all fields except ID (extended type)

I have original GQL type from intermediaries microservice.
#ObjectType('Intermediary')
#Directive('#key(fields: "id")')
export class IntermediaryType implements Intermediary {
#Field(() => ID)
id: string
#Field()
beneficiaryName: string
// and other fields
}
And another GQL type that extends from external GQL type.
#ObjectType('Intermediary')
#Directive('#extends')
#Directive('#key(fields: "id")')
export class IntermediaryType {
#Field(() => ID)
#Directive('#external')
id: string
}
GQL type that nests external GQL type:
#ObjectType('FXPayment')
export class FXPaymentType {
#Field(() => ID)
id: string
#Field(() => [IntermediaryType])
intermediaries?: IntermediaryType[]
// and other fields
}
And I have a resolver for my mutation and intermediaries field.
#Resolver(() => FXPaymentType)
export class FXPaymentsResolver {
constructor(private _fxPaymentsService: FXPaymentsService) {}
#Mutation(() => FXPaymentType)
async createFXPayment(
#Args('input') input: CreateFXPaymentInput,
): Promise<FXPaymentType> {
const createdFXPayment = await this._fxPaymentsService.createFXPayment(input)
return createdFXPayment
}
#ResolveField()
intermediaries(#Parent() fxPayment: FXPaymentEntity): IntermediaryType[] {
const usedIntermediaries = [
fxPayment.nostroRequisites,
fxPayment.providerRequisites,
fxPayment.orderingRequisites,
]
return usedIntermediaries
}
}
The problem is that when mutation executed -- nested IntermediaryType contains only id field whereas other fields are nullable. I logged data in #ResolveField() and it showed me that all fields are not in null. I tried to remove #ResolveField and allow high-level resolver to resolve intermediaries field automatically (that is I just created intermediaries in my mutation) -- it doesn't work.
But still, I do really don't know what I'm doing wrong... Any attempts to forcely assign value to these fields failed also. Could you give any helpful comments on that? What should I try to do? Is everything okay with my code or not? Any ideas? Please, help.

type-graphql make exception from Field Resolver

I have a following Entity and Resolver for example,
export class SampleA extends BaseEntity {
#Field()
#PrimaryGeneratedColumn()
id!: number;
#Field()
#Column()
title!: string
#Field()
#Column()
answer!: string;
}
#Resolver(SampleA)
export class SampleAResolver {
#FieldResolver(() => String)
async answer(#Root() sampleA: SampleA, #Ctx() {req}: MyContext) {
const msg = "answer is not public";
if(!req.session.userId) return msg;
return sampleA.answer;
}
}
It worked out well. As long as the condition does not meet, the answer field will be always "answer is not public" across the entire app. However, if sometimes I want to make a exception out of it,
For example, if I have the following
#ObjectType()
class ExceptedResponse {
#Field(() => String, {nullable: true})
errors?: string;
#Field(() => SampleA, {nullable: true})
result?: SampleA;
}
When I try to return the ExceptedResponse, the SampleA field will always be filtered by the answer #FieldResolver() so I cannot get the real answer.
So I wonder if I can make it to an exception out of it without manually re-create a whole new field like
#ObjectType()
class ExceptedResponse {
#Field(() => String, {nullable: true})
errors?: string;
#Field(() => {id: Number, title: String, answer: String }, {nullable: true})
result?: {id: number, title: string, answer: string };
}
//then I just manually assign each value inside my graphql method
Or ideally if I can make a SpecialSampleA extends from SampleA without that limitation.
After some random tests, it seems like it's extremely simple to achieve my goal.
Just simply create a new class and then assign the SampleA result to it. It's auto handled by type-graphql and escape the resolve field check because Class is changed;
#ObjectType()
class ExceptedSampleA {
#Field()
inspiration: string;
#Field()
answer: string;
}
//inside response
#Field(() => ExceptedSampleA, {nullable: true})
result?: ExceptedSampleA;
//inside graphql method like Mutation() then return response
const sampleA = SampleA.findOne();
return { result: sampleA, error: someError }

TypeGraphQL createUnionFunction with parameter

I'm trying to implement an extension of typegraphql's createUnionType() function to where I can pass in a class/ObjectType instead of hardcoding, and it will return a union type of both.
What I have so far doesn't work but I feel like it's possible. Could anyone provide any insight? Maybe it's not possible?
typedefs
import { ObjectType, Field, createUnionType, ClassType } from "type-graphql";
#ObjectType()
export default class MutationSuccess {
#Field()
success: boolean = true;
}
// Doesn't work
export const MutationResponse = (objectType: ClassType) => createUnionType({
name: 'MutationResponseType',
types: () => [MutationSuccess, objectType],
})
How I'm trying to use it in my resolver
#Resolver()
export default class RelationResolver {
#Mutation(() => MutationResponse(Relation), { description: 'follow user' })
async follow(
#Arg('relationInput') relationInput: RelationInput
): Promise<Relation | MutationSuccess> {
// do some stuff
}
}
error
UnhandledPromiseRejectionWarning: Error: Cannot determine GraphQL output type for follow
The Relation class need to be decorated with #ObjectType and the union type name has to be unique.

Resources