#TS error

4 messages · Page 1 of 1 (latest)

placid galeBOT
#

Hi @zenith root. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

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:

  1. Explicitly type the result of ctx.run* or the handler’s return type

    Instead 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.runQuery and Convex’s generated types. [Circular inference doc; Support example]

  2. Avoid using Convex helper types (Doc, Id, etc.) on values coming from ctx.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]

  1. 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]

  2. As a more drastic option: static code generation

    Enabling “static code generation (beta)” removes most inference from api/internal and can eliminate TS2589 entirely, at the cost of having to provide explicit returns validators for every function and losing a lot of inference. This is acknowledged as a trade‑off, not a perfect fix. [Static codegen discussion]

  3. Last‑resort workarounds

    • Some people temporarily use // @ts-ignore on the handler line, but that’s not ideal. [Thread notes]
    • Using require as you’re doing is another way of “turning off” inference, but it’s not the recommended pattern in the docs or support threads.

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]

If you’d like, you can paste a minimal example of your ctx.runQuery(internal.auth.users.getByEmail, ...) action and I can show exactly where to cut the inference chain without resorting to require.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: