Interface implementation object types are not exposed on schema when not used by any field
n1ru4l opened this issue · 3 comments
const GraphQLChatMessageInterfaceType = t.interfaceType<ChatMessageType>({
name: "ChatMessage",
fields: () => [
t.abstractField("id", t.NonNull(t.ID)),
t.abstractField(
"content",
t.NonNull(t.List(t.NonNull(GraphQLChatMessageNode)))
),
t.abstractField("createdAt", t.NonNull(t.String)),
t.abstractField("containsDiceRoll", t.NonNull(t.Boolean)),
],
});
const GraphQLOperationalChatMessageType = t.objectType<
OperationalChatMessageType
>({
interfaces: [GraphQLChatMessageInterfaceType],
name: "OperationalChatMessage",
fields: () => [
t.field("id", {
type: t.NonNull(t.ID),
resolve: (message) => message.id,
}),
t.field("content", {
type: t.NonNull(t.List(t.NonNull(GraphQLChatMessageNode))),
resolve: (message) => message.content,
}),
t.field("createdAt", {
type: t.NonNull(t.String),
resolve: (message) => new Date(message.createdAt).toISOString(),
}),
t.field("containsDiceRoll", {
type: t.NonNull(t.Boolean),
resolve: (message) =>
message.content.some((node) => node.type === "DICE_ROLL"),
}),
],
});
const GraphQLUserChatMessageType = t.objectType<UserChatMessageType>({
interfaces: [GraphQLChatMessageInterfaceType],
name: "UserChatMessage",
description: "A chat message",
fields: () => [
t.field("id", {
type: t.NonNull(t.ID),
resolve: (message) => message.id,
}),
t.field("authorName", {
type: t.NonNull(t.String),
resolve: (message) => message.authorName,
}),
t.field("content", {
type: t.NonNull(t.List(t.NonNull(GraphQLChatMessageNode))),
resolve: (message) => message.content,
}),
t.field("createdAt", {
type: t.NonNull(t.String),
resolve: (message) => new Date(message.createdAt).toISOString(),
}),
t.field("containsDiceRoll", {
type: t.NonNull(t.Boolean),
resolve: (message) =>
message.content.some((node) => node.type === "DICE_ROLL"),
}),
],
});
Unless I have no field that "consumes" the type GraphQLUserChatMessageType
or GraphQLOperationalChatMessageType
the both types are not available on the GraphQL Schema.
Also, I cannot find an option (in the TS Typings) for specifying to which actual object type the interface type resolves to.
I assumed there would be a similar API like with the union type e.g.:
const GraphQLDiceRollDetailNode = t.unionType<DiceRollDetail>({
name: "DiceRollDetail",
types: [
GraphQLDiceRollOperatorNode,
GraphQLDiceRollConstantNode,
GraphQLDiceRollDiceRollNode,
GraphQLDiceRollOpenParenNode,
GraphQLDiceRollCloseParenNode,
],
resolveType: (obj) => {
if (obj.type === "Operator") return GraphQLDiceRollOperatorNode;
else if (obj.type === "Constant") return GraphQLDiceRollConstantNode;
else if (obj.type === "DiceRoll") return GraphQLDiceRollDiceRollNode;
else if (obj.type === "OpenParen") return GraphQLDiceRollOpenParenNode;
else if (obj.type === "CloseParen") return GraphQLDiceRollCloseParenNode;
throw new Error("Invalid type");
},
});
Tools like graphql-tools do the same (https://www.graphql-tools.com/docs/resolvers#unions-and-interfaces).
Also in addition I want to mention that the union types + their implementations could be a bit more type-safe by requiring to return a tuple of the type + the required object for that object type. in the resolveType
function (See #12).
export type DiceRollDetail =
| {
type: "DiceRoll";
content: string;
detail: {
max: number;
min: number;
};
rolls: Array<number>;
}
| {
type: "Constant";
content: string;
}
| {
type: "Operator";
content: string;
}
| {
type: "OpenParen";
content: string;
}
| {
type: "CloseParen";
content: string;
};
const GraphQLDiceRollOperatorNode = t.objectType<
Extract<DiceRollDetail, { type: "Operator" }>
>({
name: "DiceRollOperatorNode",
fields: () => [
t.field("content", {
type: t.NonNull(t.String),
resolve: (object) => object.content,
}),
],
});
const GraphQLDiceRollDetailNode = t.unionType<DiceRollDetail>({
name: "DiceRollDetail",
types: [
GraphQLDiceRollOperatorNode,
GraphQLDiceRollConstantNode,
GraphQLDiceRollDiceRollNode,
GraphQLDiceRollOpenParenNode,
GraphQLDiceRollCloseParenNode,
],
resolveType: (obj) => {
if (obj.type === "Operator") {
obj // is Extract<DiceRollDetail, { type: "Operator" }>
return [GraphQLDiceRollOperatorNode, obj]
}
else if (obj.type === "Constant") {
obj // is Extract<DiceRollDetail, { type: "Constant" }>
return [GraphQLDiceRollConstantNode, obj];
}
else if (obj.type === "DiceRoll") {
obj // is Extract<DiceRollDetail, { type: "DiceRoll" }>
return GraphQLDiceRollDiceRollNode;
}
else if (obj.type === "OpenParen") {
obj // is Extract<DiceRollDetail, { type: "OpenParen" }>
return [GraphQLDiceRollOpenParenNode, obj];
}
else if (obj.type === "CloseParen") {
obj // is Extract<DiceRollDetail, { type: "CloseParen" }>
return [GraphQLDiceRollCloseParenNode, obj];
}
throw new Error("Invalid type");
},
});
I thought interface already have resolveType
https://github.com/sikanhe/gqtx/blob/master/src/types.ts#L157
https://github.com/sikanhe/gqtx/blob/master/src/define.ts#L215
Alternatively, I think the better way for interface implementation should be using isTypeOf
on the subtypes
Quote from graphql creator Lee Byron on this issue:
graphql/graphql-js#876 (comment)
I thought interface already have resolveType
https://github.com/sikanhe/gqtx/blob/master/src/types.ts#L157
https://github.com/sikanhe/gqtx/blob/master/src/define.ts#L215
There is no option for defining the resolveType
function with the define API with the interfaceType
function:
Lines 224 to 242 in 460bcee
Alternatively, I think the better way for interface implementation should be using isTypeOf on the subtypes
I never heard of that API actually. Looks interesting. and I will definitely check it out. Still, for completeness we should probably still provide the resolveType
function option for the interfaceType
function.
Unless I have no field that "consumes" the type GraphQLUserChatMessageType or GraphQLOperationalChatMessageType the both types are not available on the GraphQL Schema.
Any ideas on how we can tackle this? Sometimes I only have a field that returns an interface type but the interface implementation types are not directly returned by any field.
A workaround would be to add dummy fields to the Query/Mutation type that return null.
A more clean and clever solution would allow passing in those object types via the buildGraphQLSchema
function.
The API could look something like this:
import { buildGraphQLSchema } from "gqtx";
// ... other imports
export const schema = buildGraphQLSchema({
query: Query,
subscription: Subscription,
mutation: Mutation,
additionalObjectTypes: {
GraphQLDiceRollOperatorNode,
GraphQLDiceRollConstantNode,
GraphQLDiceRollDiceRollNode,
GraphQLDiceRollOpenParenNode,
GraphQLDiceRollCloseParenNode,
}
});
An even more convenient way would be to auto-detect those types by reading the (Edit: This is already happening right now). This does not work for interface types though.types
from unionType
s
Therefore the explicit way of passing them to the buildGraphQLSchema
function could be the better solution.
I will create a new issue for union types and isTypeOf typing improvements