dannyelo
dannyelo3mo ago

Type error: implicitly has type 'any' because it does not have a type annotation

Hello, I have a type error but can't figure it out why its causing it.
export const createInvoice = action({
args: {
orderId: v.id('orders'),
},
handler: async (ctx, args) => {
// ... irrelevant code

const order = await ctx.runQuery(internal.orders.getOrderByIdInternal, {
orderId: args.orderId,
})

if (!order) {
throw new Error('Order not found')
}

return order
},
})
export const createInvoice = action({
args: {
orderId: v.id('orders'),
},
handler: async (ctx, args) => {
// ... irrelevant code

const order = await ctx.runQuery(internal.orders.getOrderByIdInternal, {
orderId: args.orderId,
})

if (!order) {
throw new Error('Order not found')
}

return order
},
})
When I return the order, the variable order, createInvoice and handler turns red with a type error. (image attached).
'order' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.ts(7022)
'handler' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.ts(7023)
'createInvoice' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.ts(7022)
'order' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.ts(7022)
'handler' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.ts(7023)
'createInvoice' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.ts(7022)
What am I doing wrong?
No description
No description
28 Replies
Convex Bot
Convex Bot3mo ago
Thanks for posting in <#1088161997662724167>. Reminder: If you have a Convex Pro account, use the Convex Dashboard to file support tickets. - Provide context: What are you trying to achieve, what is the end-user interaction, what are you seeing? (full error message, command output, etc.) - Use search.convex.dev to search Docs, Stack, and Discord all at once. - Additionally, you can post your questions in the Convex Community's <#1228095053885476985> channel to receive a response from AI. - Avoid tagging staff unless specifically instructed. Thank you!
lee
lee3mo ago
Actions | Convex Developer Hub
Actions can call third party services to do things such as processing a payment
dannyelo
dannyeloOP3mo ago
This is the internal.orders.getOrderByIdInternal internalQuery function.
export const getOrderByIdInternal = internalQuery({
args: {
orderId: v.id('orders'),
},
handler: async (ctx, args) => {
const order = await ctx.db.get(args.orderId)
if (!order) {
return null
}
const orderLines = await ctx.db
.query('order_lines')
.withIndex('by_orderId', (q) => q.eq('orderId', args.orderId))
.collect()
const customer = await ctx.db.get(order.customerId)

return {
...order,
customer: {
...customer,
address: customer?.addressId
? await ctx.db.get(customer.addressId)
: null,
},
paymentForm: order.paymentFormId
? await ctx.db.get(order.paymentFormId)
: null,
paymentMethod: order.paymentMethodId
? await ctx.db.get(order.paymentMethodId)
: null,
use: order.useId ? await ctx.db.get(order.useId) : null,
orderLines: await Promise.all(
orderLines.map(async (orderLine) => {
const product = await ctx.db.get(orderLine.productId)
const productTaxesIds = product?.taxes
const productTaxes = await Promise.all(
productTaxesIds?.map(async (taxId) => {
return await ctx.db.get(taxId)
}) ?? [],
)

return {
...orderLine,
...(product ?? {}),
productCategory: product?.productCategoryId
? await ctx.db.get(product.productCategoryId)
: null,
taxes: productTaxes,
invoice: order.invoiceId ? await ctx.db.get(order.invoiceId) : null,
}
}),
),
}
},
})
export const getOrderByIdInternal = internalQuery({
args: {
orderId: v.id('orders'),
},
handler: async (ctx, args) => {
const order = await ctx.db.get(args.orderId)
if (!order) {
return null
}
const orderLines = await ctx.db
.query('order_lines')
.withIndex('by_orderId', (q) => q.eq('orderId', args.orderId))
.collect()
const customer = await ctx.db.get(order.customerId)

return {
...order,
customer: {
...customer,
address: customer?.addressId
? await ctx.db.get(customer.addressId)
: null,
},
paymentForm: order.paymentFormId
? await ctx.db.get(order.paymentFormId)
: null,
paymentMethod: order.paymentMethodId
? await ctx.db.get(order.paymentMethodId)
: null,
use: order.useId ? await ctx.db.get(order.useId) : null,
orderLines: await Promise.all(
orderLines.map(async (orderLine) => {
const product = await ctx.db.get(orderLine.productId)
const productTaxesIds = product?.taxes
const productTaxes = await Promise.all(
productTaxesIds?.map(async (taxId) => {
return await ctx.db.get(taxId)
}) ?? [],
)

return {
...orderLine,
...(product ?? {}),
productCategory: product?.productCategoryId
? await ctx.db.get(product.productCategoryId)
: null,
taxes: productTaxes,
invoice: order.invoiceId ? await ctx.db.get(order.invoiceId) : null,
}
}),
),
}
},
})
Hey @lee, thanks for the resource. Why the type is not inferred? The return type of my función is big. Do I need create a TS type or interface mapping the returned object? Or what recommended pattern is best for this case?
lee
lee3mo ago
i would search "circular type" in discord. this is how i understand it https://discord.com/channels/1019350475847499849/1236968484512989234/1237339765418758195 i don't know if there's a better way than annotating the return type. your type does look complex, that's a bummer. maybe @ballingt has a solution?
dannyelo
dannyeloOP3mo ago
@lee @ballingt I create a "function return type" from my internal function, and the type is working, but the error still exists.
export type OrderInternal = Awaited<ReturnType<typeof getOrderByIdInternal>>

export const getOrderByIdInternal = internalQuery({
args: {
orderId: v.id('orders'),
},
handler: async (ctx, args) => {
const order = await ctx.db.get(args.orderId)
if (!order) {
return null
}
// ...rest of the code
return {
// ...big object
}
export type OrderInternal = Awaited<ReturnType<typeof getOrderByIdInternal>>

export const getOrderByIdInternal = internalQuery({
args: {
orderId: v.id('orders'),
},
handler: async (ctx, args) => {
const order = await ctx.db.get(args.orderId)
if (!order) {
return null
}
// ...rest of the code
return {
// ...big object
}
This is how I'm using it, but if I return the order, I got an error in the function variable and in handler property.
const order: OrderInternal = await ctx.runQuery(
internal.orders.getOrderByIdInternal,
{
orderId: args.orderId,
},
)

return order
const order: OrderInternal = await ctx.runQuery(
internal.orders.getOrderByIdInternal,
{
orderId: args.orderId,
},
)

return order
ballingt
ballingt3mo ago
Is the error about the output not matching the output: v... validator? Or somethin gelse?
dannyelo
dannyeloOP3mo ago
I'm trying to returning a value in an action that depends on the result of calling ctx.runQuery or ctx.runMutation.
dannyelo
dannyeloOP3mo ago
Actions | Convex Developer Hub
Actions can call third party services to do things such as processing a payment
dannyelo
dannyeloOP3mo ago
As Lee point out this link
ballingt
ballingt3mo ago
When you say the error still exists, could you show a screenshot of the error? There are a few ways it could manifest. (re "got an error in the function variable and in handler property.") Big picture we need to make this more convenient, and some changes to the way we declare functions may help. But it's going to be a while for big changes like that, until then we need clear workarounds. The general category of declaring types is usually enough, but that's vague, there are several places types could be declared
erquhart
erquhart3mo ago
The type you're providing for order is superfluous, it's the return type of the query you're running from inside the mutation so it's still circular inference. You'll have to actually type out the object in this case without referencing the query that's creating the object. I've found in many cases like this that I don't actually need the whole object I'm returning, and only need a few keys. If that happens to be the case the return value will be simpler to type. But either way, you have to provide an independent type for any part of that query reponse that goes into your action response.
dannyelo
dannyeloOP3mo ago
Hey @ballingt I attached a screen shot of the error at the beginning of this post In this case I do need the whole object I'm returning. I tried to type out the object, not with all properties, but with some and the error still there
dannyelo
dannyeloOP3mo ago
No description
No description
No description
dannyelo
dannyeloOP3mo ago
@erquhart
erquhart
erquhart3mo ago
Are there any other type errors in your Convex code? Especially inference errors like this one
dannyelo
dannyeloOP3mo ago
No, those are the only ones @erquhart
erquhart
erquhart3mo ago
This shouldn't work, but try using a separate variable and typing that, eg.:
const order = await ctx.runQuery(...)
const typedOrder: OrderTest = order
return order
const order = await ctx.runQuery(...)
const typedOrder: OrderTest = order
return order
I am seeing that simply typing it is not enough, it gets tricky when the query return value is complex. Or, to rephrase, typing is enough, but how to do that correctly gets tricky, and the compiler is often unable to help due to the inference issue.
dannyelo
dannyeloOP3mo ago
Thank you @erquhart
erquhart
erquhart3mo ago
Can that getOrderByIdInternal query return null for any reason? Playing around with it and I think that specific case can cause problems Oh, better yet, can it return two different types? Yeah it looks like a union return type on the query would still break things, even with accurate typing.
dannyelo
dannyeloOP3mo ago
Yes it breaks It does not like returning the result of any runMutation, runQuery or runAction. Or better said, to depend on the returning result of those.
erquhart
erquhart3mo ago
Minimal repro:
export const testQuery = query({
handler: async (ctx) => {
if ("".length) {
return 42;
}
return "bar";
},
});

export const testAction = action({
// 'handler' implicitly has return type 'any' because it
// does not have a return type annotation and is referenced
// directly or indirectly in one of its return expressions.
// ts(7023)
handler: async (ctx) => {
const value: number | string = await ctx.runQuery(
api.app.testQuery,
);
return value
},
});
export const testQuery = query({
handler: async (ctx) => {
if ("".length) {
return 42;
}
return "bar";
},
});

export const testAction = action({
// 'handler' implicitly has return type 'any' because it
// does not have a return type annotation and is referenced
// directly or indirectly in one of its return expressions.
// ts(7023)
handler: async (ctx) => {
const value: number | string = await ctx.runQuery(
api.app.testQuery,
);
return value
},
});
dannyelo
dannyeloOP3mo ago
it does give you a type error
erquhart
erquhart3mo ago
Yep, I don't know how to work around it in the repro I just gave. Still trying though.
dannyelo
dannyeloOP3mo ago
👍 I think it is common to want to return something that depends on the result of runMutation/Query/Action
erquhart
erquhart3mo ago
Ah, typing the return for the whole action, forgot about that:
export const testQuery = query({
handler: async (ctx) => {
if ("".length) {
return 42;
}
return "bar";
},
});

export const testAction = action({
handler: async (ctx): Promise<number | string> => {
const value = await ctx.runQuery(
api.app.testQuery,
);
return value
},
});
export const testQuery = query({
handler: async (ctx) => {
if ("".length) {
return 42;
}
return "bar";
},
});

export const testAction = action({
handler: async (ctx): Promise<number | string> => {
const value = await ctx.runQuery(
api.app.testQuery,
);
return value
},
});
That ^^ has no errors
dannyelo
dannyeloOP3mo ago
So typing also the returned value type*
erquhart
erquhart3mo ago
If you type the return value of the action, you don't need to type the return value of runQuery
dannyelo
dannyeloOP3mo ago
That worked for me too!

Did you find this page helpful?