#TS error
4 messages · Page 1 of 1 (latest)
This is a known TypeScript limitation that shows up with Convex’s generated types when there’s circular type inference, especially around ctx.runQuery / ctx.runMutation / ctx.runAction and large schemas. It’s not specific to your schema size, and other users have hit it in similar situations. [Type instantiation thread; Excessive deep thread]
The core issue: if an action/query/mutation calls another Convex function via ctx.run* and then returns that value (or something inferred from it) without breaking the inference chain, TypeScript can end up in an “excessively deep” loop. Using require works only because it breaks type inference entirely.
From the docs and support threads, the recommended / “official” ways to handle this are:
-
Explicitly type the result of
ctx.run*or the handler’s return typeInstead of relying on inference from
internal.auth.users.getByEmail, give the result a concrete, non‑Convex type, or type the handler’s return:export const myAction = action({ args: { email: v.string() }, handler: async (ctx, args): Promise<{ id: string | null }> => { const user: { id: string | null } = await ctx.runQuery(internal.auth.users.getByEmail, { email: args.email }); return { id: user.id }; }, });The key is: don’t let the return type be inferred through
ctx.runQueryand Convex’s generated types. [Circular inference doc; Support example] -
Avoid using Convex helper types (
Doc,Id, etc.) on values coming fromctx.run*
Those helpers themselves depend on generated types and can keep you in the circular chain. Use plain object types instead when typing the result of ctx.run*. [Very fun instance; Support explanation]
-
Slim down what you return from actions that call other Convex functions
If you only need a few fields from
getByEmail, project and type just those fields instead of returning the whole object. [Support suggestion] -
As a more drastic option: static code generation
Enabling “static code generation (beta)” removes most inference from
api/internaland can eliminate TS2589 entirely, at the cost of having to provide explicitreturnsvalidators for every function and losing a lot of inference. This is acknowledged as a trade‑off, not a perfect fix. [Static codegen discussion] -
Last‑resort workarounds
- Some people temporarily use
// @ts-ignoreon the handler line, but that’s not ideal. [Thread notes] - Using
requireas you’re doing is another way of “turning off” inference, but it’s not the recommended pattern in the docs or support threads.
- Some people temporarily use
There isn’t a separate “official API” to import internal/api that avoids this; the recommended path is still import { internal, api } from "./_generated/api", and then breaking circular inference with explicit types as above. [Circular inference doc]
As for whether this is being worked on: the threads acknowledge it as a common pain point and mention static codegen as one mitigation, but there’s no explicit statement in these sources about a new, upcoming fix beyond those techniques. [Excessive deep thread]