#TS2589

10 messages · Page 1 of 1 (latest)

viscid goblet
#

convex and TS2589, any best practices?

finite aspenBOT
#

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

#

TS2589 (“Type instantiation is excessively deep and possibly infinite”) with Convex almost always comes from circular type inference around ctx.runQuery / ctx.runMutation / ctx.runAction and Convex’s generated types.

From the sources, here are the concrete best practices:

1. Don’t rely on inferred return types through ctx.run*

If an action’s return type depends (even indirectly) on ctx.runQuery / ctx.runMutation, TypeScript can’t resolve the types and you get TS2589. [Circular inference]

Instead, break the inference loop by adding an explicit type annotation:

// Option 1: type the handler return
export const myAction = action({
  args: {},
  handler: async (ctx): Promise<null> => {
    const result = await ctx.runQuery(api.myFunctions.getSomething);
    return result;
  },
});

or

// Option 2: type the result of ctx.runQuery
export const myAction = action({
  args: {},
  handler: async (ctx) => {
    const result: null = await ctx.runQuery(api.myFunctions.getSomething);
    return result;
  },
});

This keeps TypeScript from trying to infer the action’s return type through the Convex-generated types of the called function. [Circular inference]

2. Don’t use Convex-generated types in those annotations

From the Discord threads: if your explicit types use Doc, Id, or anything from convex/_generated, you’re still in the same circular graph. [TS2589 thread; TS2589 thread 2]

So for handlers that call ctx.run*:

  • Avoid:
#
  // This can still cause TS2589
  const requisitionInDb: Doc<"requisitions"> =
    await ctx.runQuery(internal.some.query, { ...args });
  return requisitionInDb;
  • Prefer:

    • Return a manually-typed subset of fields:

      const requisitionInDb: {
        field: string;
        otherField: number;
      } =
        (await ctx.runQuery(internal.some.query, { ...args })) || {};
      return requisitionInDb;
      
    • Or if you only need a couple of props, just type those:

      const ongoingBookings: { usedProp: string; otherUsedProp: number }[] =
        await ctx.runQuery(internal.services.booking.getOngoing, {
          userId: ctx.user._id,
        });
      

This “minimal typing” approach is explicitly recommended in the Discord guidance. [TS2589 thread; TS2589 thread 2]

3. returns validators don’t fix TS2589

Adding a returns: v.* validator on the Convex function does not break the circular inference; the problem is at the TypeScript level in the handler’s return type / ctx.run* call. [TS2589 thread 1; TS2589 thread 2]

You must:

  • Type the handler return or the ctx.run* result explicitly, and
  • Ensure that explicit type does not depend on Convex-generated types.

4. How to track down the offending code

From support’s advice:

#
  • Search for all usages of ctx.runQuery, ctx.runMutation, ctx.runAction.
  • Focus on recently changed ones.
  • For each, ensure:
    • The handler’s return type is explicitly annotated, or
    • The ctx.run* result is explicitly typed,
    • And those annotations don’t use Doc, Id, or other generated types. [TS2589 thread]

If you paste a specific function that’s triggering TS2589, I can help rewrite it using these patterns.

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

#

@viscid goblet Kapa.ai is still learning and improving, please let me know how I did by reacting below.

somber plover
#

Convex 1.33.0 has a fix for one cause of this

viscid goblet
#

@somber plover only the same folder & file combo issue right?

Our issue could possibly be extensive use of ctx.run* and deep types on our functions

Atm our convex experience is getting fairly bad as we’re losing the end to end types via these workarounds..

somber plover
#

Are you adding return type annotations to your handler functions? That almost always fixes it for me

viscid goblet