I want to return different field data types (for byte field string or array) through a single query on Graphql. However,
'Abstract type "GetRecordsWrapper" must resolve to an Object type at runtime for field "Query.records". Either the "GetRecordsWrapper" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.'
I get the error. To solve the problem, I added the __resolveType
function to the Resolver class, but the same error still persists.
Records Resolver:
import { Resolver, Query, Mutation, Args, Subscription } from '@nestjs/graphql';
import { RecordsService } from './records.service';
import { PubSub } from 'graphql-subscriptions';
import { CreateRecordInput, UpdateRecordInput} from '../graphql';
const pubSub = new PubSub();
@Resolver('Record')
export class RecordsResolver {
constructor(private readonly recordsService: RecordsService) {}
__resolveType(obj: any) {
if (typeof obj.byte === 'string') {
return 'HexByteStringResponse';
}
if (Array.isArray(obj.byte)) {
return 'ByteArrayResponse';
}
return null;
}
@Query('records')
async records(@Args('format') args: "byte" | "array"): Promise<any> {
return this.recordsService.findAll(args);
}
Records Service:
import { Injectable } from '@nestjs/common';
import { Record } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { CreateRecordInput, UpdateRecordInput, GetRecordsWrapper, HexByteStringResponse, ByteArrayResponse } from 'src/graphql';
@Injectable()
export class RecordsService {
constructor(private prisma: PrismaService) {}
async findAll(format: "byte" | "array"): Promise<GetRecordsWrapper[]> {
const records = await this.prisma.record.findMany({
include: {
provider: true,
},
});
return records.map(record => {
if (format === "byte") {
return {
...record,
byte: `0x${record.byte.toString('hex')}`,
} as HexByteStringResponse;
} else {
return {
...record,
byte: Array.from(record.byte),
} as ByteArrayResponse;
}
});
}
Graphql Schema includes Union:
scalar DateTime
scalar Bytes
scalar Provider
type Record {
id: ID!
byte: Bytes!
sendedAt: DateTime!
createdAt: DateTime!
provider: Provider!
}
type HexByteStringResponse {
id: ID!
byte: String!
sendedAt: DateTime!
createdAt: DateTime!
provider: Provider!
}
type ByteArrayResponse {
id: ID!
byte: [Int!]!
sendedAt: DateTime!
createdAt: DateTime!
provider: Provider!
}
union GetRecordsWrapper = HexByteStringResponse | ByteArrayResponse
type Query {
records(format: String!): [GetRecordsWrapper]!
record(id: String!): GetRecordsWrapper
recordsWrapper(type: String!): GetRecordsWrapper
}
You'd need to put __resolveType
on the resolvers of GetRecordsWrapper
, not on those of the unrelated Record
. Or maybe the whole class should be @Resolver('GetRecordsWrapper') export class GetRecordsWrapperService { …
instead of @Resolver('Record') export class RecordsService { …
, given its method that implements Query.records
despite not returning any Record
s?
However, there's a simpler solution than providing a __resolveType
method on the union type. You can just return each object with a __typename
property, and the default __resolveType
implementation will use that.
return records.map(format === "byte"
? (record): HexByteStringResponse => ({
...record,
__typename: 'HexByteStringResponse',
byte: `0x${record.byte.toString('hex')}`,
})
: (record): ByteArrayResponse => ({
...record,
__typename: 'ByteArrayResponse',
byte: Array.from(record.byte),
})
);