#Convex context generic type

3 messages · Page 1 of 1 (latest)

glass bane
#

What is the recommended approach when having a helper function like:

async function loadA(ctx: QueryCtx): Promise<Doc<"A"> | null> {
  return ctx.db
    .query("A")
    .withIndex("by_key", (q) => q.eq("key", "x"))
    .unique();
}

and now needing to call it from a mutation?

twilit troutBOT
#

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

#

Use a helper that takes the least powerful context it needs, and rely on the fact that MutationCtx is a superset of QueryCtx.

Convex documents explicitly recommend:

  • Define helpers in convex/ that take QueryCtx / MutationCtx / ActionCtx as needed. [TS helpers]
  • A MutationCtx “also satisfies the QueryCtx interface”, so any helper that accepts QueryCtx can be called from both queries and mutations. [TS helpers]
  • Mutations can call helpers typed with QueryCtx. [Mutation helpers]

So your helper is already correctly typed:

import { QueryCtx } from "./_generated/server";
import type { Doc } from "./_generated/dataModel";

export async function loadA(ctx: QueryCtx): Promise<Doc<"A"> | null> {
  return ctx.db
    .query("A")
    .withIndex("by_key", (q) => q.eq("key", "x"))
    .unique();
}

You can call it from:

  • A query:
export const someQuery = query({
  args: {},
  handler: async (ctx) => {
    return await loadA(ctx); // ctx is QueryCtx
  },
});
  • A mutation:
export const someMutation = mutation({
  args: {},
  handler: async (ctx) => {
    const a = await loadA(ctx); // ctx is MutationCtx, which is compatible with QueryCtx
    // ...
  },
});

No generics or overloads are needed; just type the helper with QueryCtx and reuse it from both.

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