That are in a separate file from my server
export class CartItem {
id: string;
name: string;
price: number;
quantity: number;
constructor(id: string, name: string, price: number, quantity: number) {
this.id = id;
this.name = name;
this.price = price;
this.quantity = quantity;
}
}
export class Cart {
id: string;
items?: CartItem[];
constructor(id: string, items?: CartItem[]) {
this.id = id;
this.items = items;
}
}
export class Money {
amount: number;
formatted: string;
constructor(amount: number, formatted: string) {
this.amount = amount;
this.formatted = formatted;
}
}
I have two comments showing where I am likely failing
import { createServer } from '@graphql-yoga/node'
import SchemaBuilder from "@pothos/core"
import { CartItem, Cart, Money } from 'gql';
const CARTS = [
{
id: '1',
items: [
{
id: '1',
name: 'Item 1',
price: 10,
quantity: 1
},
{
id: '2',
name: 'Item 2',
price: 20,
quantity: 2
}
]
},
{
id: '2',
items: [
{
id: '3',
name: 'Item 3',
price: 30,
quantity: 3
},
{
id: '4',
name: 'Item 4',
price: 40,
quantity: 4
}
]
}
]
const builder = new SchemaBuilder({});
builder.objectType(Cart, {
name: "Cart",
description: "A cart",
fields: (t) => ({
id: t.exposeString('id', {}),
items: t.field({
type: [CartItem],
resolve: (cart) => cart.items ?? [],
}),
// This is the field that we want to USE TO REFERENCE
// subTotal: t.field({
// type: Money,
// resolve: (cart) => {
// const total = cart.items?.reduce((acc, item) => acc + item.price * item.quantity, 0) ?? 0;
// return new Money(total, `$${total}`);
// }
// })
}),
});
builder.objectType(CartItem, {
name: "CartItem",
description: "A cart item",
fields: (t) => ({
id: t.exposeString('id', {}),
name: t.exposeString('name', {}),
price: t.exposeInt('price', {}),
quantity: t.exposeInt('quantity', {}),
}),
});
// make a reference to the Money type THAT DOESEN'T WORK
const MoneyType = builder.objectRef<MoneyShape>("Money");
builder.objectType(Money, {
name: "Money",
description: "A money",
fields: (t) => ({
amount: t.exposeInt('amount', {}),
formatted: t.field({
type: String,
resolve: (money) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(money.amount),
}),
}),
});
builder.queryType({
fields: (t) => ({
cart: t.field({
type: Cart,
nullable: true,
args: {
id: t.arg.id({ required: true, description: "the id of the cart" }),
},
resolve: (_, { id }) => {
const cart = CARTS.find((cart) => cart.id === id);
if (!cart) {
throw new Error(`Cart with id ${id} not found`)
}
return cart
}
}),
carts: t.field({
type: [Cart],
resolve: () => CARTS
}),
}),
})
const server = createServer({
endpoint: '/api',
schema: builder.toSchema(),
})
export default server;
Unfortunately Pothos' error messages are not very good when it comes to the type property. Scalars in Pothos are referenced by their name, so you should put them in quotation marks:
builder.objectType(Money, {
name: "Money",
description: "A money",
fields: (t) => ({
amount: t.exposeInt("amount", {}),
formatted: t.field({
type: "String",
resolve: (money) =>
new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(money.amount),
}),
}),
});
Alternatively, you can also use t.string
:
builder.objectType(Money, {
name: "Money",
description: "A money",
fields: (t) => ({
amount: t.exposeInt("amount", {}),
formatted: t.string({
resolve: (money) =>
new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(money.amount),
}),
}),
});
Some additional hints:
I would move the formatter out of the resolver as you only have to create one instance.
I personally don't like the class way of doing things in Pothos, as my types mostly come from Prisma. It works really well, if you have service classes for every object type, but if you just have them to wrap things, it is a lot of overhead. You could consider removing the Money class and using number instead:
const moneyFormatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
})
const Money = builder.objectRef<number>('Money').implement({
description: "A money",
fields: (t) => ({
amount: t.int({ resolve: money => money }),
formatted: t.string({
resolve: (money) =>
moneyFormatter.format(money),
}),
}),
});