Drips
Drips4mo ago

Type instantiation is excessively deep and possibly infinite.

I feel like a complete noob - I’m sorry. I’m in a migration from supabase, which I used with zod, to convex. And I now often times run into this TS error. Which right now I don’t know to fix because I don’t understand what’s the actual problem. Claude is simply removing all types says it’s any and calls it a day which of cause is bad. Also interesting sometimes the error is there but then disappears by itself. So can I fine tune my linting for this?
21 Replies
Convex Bot
Convex Bot4mo 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!
Snazzie
Snazzie4mo ago
yep my entire convex folder just lit up with errors crazy this is horrible dev experience even if i explictely type the response whats weird is that it initially is able to detect the types and it then crashes out
Snazzie
Snazzie4mo ago
like this for example
No description
Snazzie
Snazzie4mo ago
the bottom variable is able to resolve its type. but the same func call at top cant exact same func call
erquhart
erquhart4mo ago
You can't return requisitionInDb from a convex function because it's the return type of another convex function, which creates the infinite type loop. More here: https://docs.convex.dev/functions/actions#dealing-with-circular-type-inference You need to type the enclosing function in a way that does not rely on type inference through the ctx.run* call. Which means something like const requisitionInDb: Doc<'requisitions'> = await ctx.runQuery(...) won't work, as Doc and friends still rely on Convex generated types. I usually try to slim down the return value if possible to simplify these cases. So, for example, if you don't actually need the whole requisitionInDb object, but maybe just a few props, just type those props explicitly inline and return those. But if you need the whole object the solution looks similar either way.
// Return partial
const {
field: string,
otherField: number
} = (await ctx.runQuery(internal.some.query, { ...args })) || {}
return { field, otherField }

// Return full doc
const requisitionInDb: {
every: string,
field: number,
typed: string,
here: number
} = (await ctx.runQuery(internal.some.query, { ...args })) || {}
return requisitionInDb
// Return partial
const {
field: string,
otherField: number
} = (await ctx.runQuery(internal.some.query, { ...args })) || {}
return { field, otherField }

// Return full doc
const requisitionInDb: {
every: string,
field: number,
typed: string,
here: number
} = (await ctx.runQuery(internal.some.query, { ...args })) || {}
return requisitionInDb
@Drips where you at on this? First step is to look for all instances of ctx.run in your convex code. The source of that type error is generally going to be one of these. Do you have a lot of them?
Drips
DripsOP4mo ago
Is 131 a lot ?😁
erquhart
erquhart4mo ago
lol maybe - possibly a sign of an anti-pattern unless your project is really big or does a lot with external services I'd look at changes since the last time your code type checked properly to bring down the scope
Drips
DripsOP4mo ago
Project is really big and also uses external services. 😁 As I’m refactoring from supabase to convex it’s kinda never “worked” I’m still in transition.
Eliot Gevers
Eliot Gevers4mo ago
Migration from supabase to convex is hell, I saw they write some blog post recently. You might want to take a look at that.
Drips
DripsOP4mo ago
I thought I did read that article but did not saw something about my problem. The fun part is. In my ide it’s an error in my local vercel build it’s an error but when I push to vercel it’s not a error 🫠
Gorka Cesium
Gorka Cesium4mo ago
since I started vibecoding i also have a lot of these errors
Snazzie
Snazzie3mo ago
ive gone through and explicitely typed almost all my handler responses. yet still getting this problem. i have to run without typecheck otherwise no progress will be made lol
erquhart
erquhart3mo ago
Can you share an example function with explicit typing Specifically, if you use a return validator instead of directly defining the return type of the handler function itself, it won't address the error. Just checking to see if that's the case anywhere
Snazzie
Snazzie3mo ago
convex/src/manualAssets.ts:312:12 - error TS2589: Type instantiation is excessively deep and possibly infinite.

312 handler: async (ctx, args): Promise<void> => {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

export const deleteManualAsset = mutation({
args: {
assetId: v.id("manualAssets"),
},
handler: async (ctx, args): Promise<void> => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error("Not authenticated");
}

const asset = await ctx.db.get(args.assetId);

if (!asset) {
throw new Error("Asset not found");
}

// Verify the asset belongs to the authenticated user
if (asset.userId !== identity.subject) {
throw new Error("Not authorized to delete this asset");
}

await ctx.db.delete(args.assetId);
},
});
convex/src/manualAssets.ts:312:12 - error TS2589: Type instantiation is excessively deep and possibly infinite.

312 handler: async (ctx, args): Promise<void> => {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

export const deleteManualAsset = mutation({
args: {
assetId: v.id("manualAssets"),
},
handler: async (ctx, args): Promise<void> => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error("Not authenticated");
}

const asset = await ctx.db.get(args.assetId);

if (!asset) {
throw new Error("Asset not found");
}

// Verify the asset belongs to the authenticated user
if (asset.userId !== identity.subject) {
throw new Error("Not authorized to delete this asset");
}

await ctx.db.delete(args.assetId);
},
});
erquhart
erquhart3mo ago
If you comment that mutation out completely does your convex code deploy successfully The code in that function won't cause that error on it's own.
Snazzie
Snazzie3mo ago
i have over 100 infinite errors while running with typecheck. if i disable typecheck. everything pushes and works Found 170 errors in 19 files.
erquhart
erquhart3mo ago
You'll need to look for places where ctx.db.runQuery/runMutation/runAction are used. A function that uses one of those functions, and then includes any part of the result in it's response, can trigger this. Depending on the function and it's relation to the rest of your code, the error can cascade all over the place. You probably just have one or a few instances of this, despite the scary 170 error / 19 file stuff
// This will error
export const myFunc = mutation({
args: {},
handler: async (ctx) => {
const value = await ctx.runQuery(api.some.func);
return value.aBoolean
},
});

// No error if you type the runQuery return value (just the part you use)
export const myFunc = mutation({
args: {},
handler: async (ctx) => {
const value: { aBoolean: boolean } = await ctx.runQuery(api.some.func);
return value.aBoolean
},
});

// No error if you type the handler return value
export const myFunc = mutation({
args: {},
handler: async (ctx): Promise<boolean> => {
const value = await ctx.runQuery(api.some.func);
return value.aBoolean
},
});
// This will error
export const myFunc = mutation({
args: {},
handler: async (ctx) => {
const value = await ctx.runQuery(api.some.func);
return value.aBoolean
},
});

// No error if you type the runQuery return value (just the part you use)
export const myFunc = mutation({
args: {},
handler: async (ctx) => {
const value: { aBoolean: boolean } = await ctx.runQuery(api.some.func);
return value.aBoolean
},
});

// No error if you type the handler return value
export const myFunc = mutation({
args: {},
handler: async (ctx): Promise<boolean> => {
const value = await ctx.runQuery(api.some.func);
return value.aBoolean
},
});
Snazzie
Snazzie3mo ago
i think this is a bit of an anti pattern no?
const value: { aBoolean: boolean } = await ctx.runQuery(api.some.func);
return value.aBoolean
},
const value: { aBoolean: boolean } = await ctx.runQuery(api.some.func);
return value.aBoolean
},
api.some.function should be providing the response type.
Snazzie
Snazzie3mo ago
its quite frustrating that even typing the handler response, the type system is failing me.
No description
No description
Clever Tagline
Clever Tagline3mo ago
One thing that might help is turning some query functions into helper functions. For example, not everything that queries the DB has to be defined with query. You can pass the query context to a function, along with other arguments the function might need, and that helper function can use the context to query the DB as if it were inside the query definition itself. More on this here: https://docs.convex.dev/understanding/best-practices/other-recommendations#use-helper-functions-to-write-shared-code
Other Recommendations | Convex Developer Hub
Additional Convex development recommendations including TypeScript usage, helper functions, database patterns, and UI optimization techniques.
erquhart
erquhart3mo ago
api.some.function does provide the response type, but when you start returning functions within functions, the Convex generated types start referencing themselves in a circular fashion. That's when this becomes necessary. Hence this bit from the docs that I linked earlier - make sure to click the blue box to expand it for more info on this: https://docs.convex.dev/functions/actions#dealing-with-circular-type-inference You'll have to find the various places where this circular type dependency is happening and address them to get rid of the cascading effects. It's a pain when you first deal with it, but in my experience, you develop an eagle eye for it moving forward.

Did you find this page helpful?