#run wuery inside an action strange behavior

1 messages · Page 1 of 1 (latest)

distant summit
#
"use node";
import { v } from "convex/values";
import { internal } from "./_generated/api";
import { action, internalAction } from "./_generated/server";

// Action - orchestrates everything
export const fetchCharacter = action({
  args: {
    id: v.number(),
  },
  handler: async (ctx, { id }) => {
    // First, check if it exists
    const existing = await ctx.runQuery(internal.character.getCharacter, {
      id,
    });

    if (existing) {
      return existing;
    }

    //if not fetch
  },
});

if in the handler i put the return existing the type definition become any otherwise its normal

lapis slateBOT
#

Hi @distant summit. 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 issue with Convex called circular type inference when an action’s return type depends on ctx.runQuery / ctx.runMutation / ctx.runAction. [Circular inference]

In your example:

const existing = await ctx.runQuery(internal.character.getCharacter, { id });

if (existing) {
  return existing; // <- this line triggers the circular inference
}

Because fetchCharacter’s return type is inferred from existing, and existing’s type is inferred from getCharacter, and that in turn depends on generated Convex types, TypeScript ends up in a loop and falls back to any.

You fix it by breaking the inference loop in one of two ways:

Option 1: Type the handler’s return

export const fetchCharacter = action({
  args: {
    id: v.number(),
  },
  handler: async (ctx, { id }): Promise<YourCharacterType | null> => {
    const existing = await ctx.runQuery(internal.character.getCharacter, {
      id,
    });

    if (existing) {
      return existing;
    }

    // if not fetch
    return null;
  },
});

Option 2: Type the result of runQuery

export const fetchCharacter = action({
  args: {
    id: v.number(),
  },
  handler: async (ctx, { id }) => {
    const existing: YourCharacterType | null =
      await ctx.runQuery(internal.character.getCharacter, { id });

    if (existing) {
      return existing;
    }

    // if not fetch
    return null;
  },
});
#

TypeScript will still check that YourCharacterType | null matches what getCharacter actually returns, so you don’t lose type safety. [Circular inference]

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

#

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