Why does field of extended type resolve to null? - Apollo Federation - apollo-server

Consider 2 services (A and B). B extends a type that is defined in A, but also adds a new field that's not in A. However, the resolvers in A that return that type don't resolve that field that B introduced (returns null). The Apollo Docs mention that B is supposed to resolve the field since B introduced it, but this doesn't seem to be working correctly because the field always resolves to null.
I'm using TS, TypeGraphql and have a config similar to the example docs.
The types look like:
Service A
#Directive(`#key(fields: "_id")`)
#ObjectType()
export class Foo {
#Field(() => String)
readonly _id!: ObjectId;
// this field resolves fine because it was defined in A
#Field(() => Bar, { nullable: true })
bar?: Bar;
}
Service B
#Directive('#extends')
#Directive(`#key(fields: "_id")`)
#ObjectType()
export class Foo {
#Field(() => String)
#Directive('#external')
readonly _id!: ObjectId;
// this field resolves to null
#Field(() => Baz, { nullable: true })
baz?: Baz;
}
Does anyone have any suggestions on what the issue could be / have any solutions in mind?

Related

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.

TypeGraphql Field and Arg decorators using custom type

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;
}

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 }

NestJS GraphQL federation circular resolvers

I am working on an existing GraphQL service, that I have successfully broken down to smaller services, using apollo federation. I have some types being extended by other services and everything works just fine. However, as I followed this example: https://docs.nestjs.com/graphql/federation now I have a circular reference kind of problem.
So basically I have, for example two types:
#ObjectType()
#Directive('#key(fields: "id")')
export class Original {
#Field(type => ID)
id: string;
...
}
// extending it in the other service
#ObjectType()
#Directive('#extends')
#Directive('#key(fields: "id")')
export class Original {
#Field(type => ID)
#Directive('#external')
id: string;
#Field(type => [Other])
#Directive('#requires(fields: "id")')
others: Other[];
...
}
#ObjectType()
#Directive('#key(fields: "id")')
export class Other {
#Field(type => ID)
id: string;
...
#Field(type => Original, { nullable: true })
original?: Original;
}
And I have two resolvers, both in the service extending the original type:
#Resolver(of => Original)
export class OriginalResolver {
...
#ResolveField(returns => [Other])
async others(#Parent() original: Original) {
const { id} = original;
...
}
}
#Resolver(of => Other)
export class OtherResolver {
...
#ResolveField((of) => Original)
async original(#Parent() other: Other) {
return { __typename: 'Orignal', id: other.original.id };
}
}
As the resolvers suggest, I can have a query with something like this:
...,
original{
others{
original{
*and so on...*
}
}
}
I don't want this circular query to be possible and I am trying to remove it, but so far I had no luck. If I simply remove the "original" field resolver, where it should return the __typename, apollo just won't extend the original type anymore. I guess that line is what basically connects the two services to find the original type, but I am not that deep in apollo so far...
So my question is how could I remove that resolver all together OR if that just has to be there for apollo to work, is there any way to "hide it"?
Thanks in advance and feel free to ask for any more info you might need.
It's fully legal to have 'loops' in GraphQL (notice 'graph'). GraphQL 'by design' gives the ability to freely shape the query [and structure of the response] including 'loops' creation.
I wouldn't say it's 'circular reference kind of problem'. It can be an efficiency/performance problem ... **This is not an evil ... when not abused.
You can use some metrics to limit API usage, restrict 'max resolving depth level'/etc.
In this case, you can simply return null in original resolver when parent is others type. This way original can be queried only on query top/root level.

NestJs/Graphql: "CustomObject" defined in resolvers, but not in schema

I'm having an issue after updating nest/core/common/graphql, in which a single type definition is not showing up in the schema. I'm consistently getting an error that it doesn't exist, though I can't find any difference between it's definition and all of the others in my application. It could totally be some ridiculous thing I am overlooking, but I am getting no other errors. I've attached the type def below. If I understood how this was being generated with the new "autoSchemaFile: true," syntax I might be able to track down where the issue is coming from.
import { Field, ObjectType, ID } from '#nestjs/graphql';
import { ObjectID } from 'mongodb';
#ObjectType()
export class CustomObject {
#Field(type => ID, { nullable: true })
id?: ObjectID;
#Field(type => String, { nullable: true })
otherField?: string;
}

Resources