#eslint for calling mutation, query or aciton directly
4 messages · Page 1 of 1 (latest)
Hi @dense surge. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:
You're asking about ESLint rules to prevent directly calling Convex functions (mutations, queries, or actions) from other Convex functions, which is considered an anti-pattern.
ESLint Configuration
You can set up ESLint to enforce best practices by preventing direct imports of raw Convex functions. Here's how to configure it:
"no-restricted-imports": [
"error",
{
patterns: [
{
group: ["*/_generated/server"],
importNames: ["query", "mutation", "action"],
message: "Use functions.ts for query, mutation, action",
},
],
},
],
This rule will throw an error whenever someone imports query, mutation, or action directly from files ending in _generated/server. You might also want to include internalQuery, internalMutation, and internalAction in this list.
Stack: Set up ESLint for best practices
Why Direct Calling is an Anti-Pattern
Calling Convex functions directly (like await someFunction(ctx, args) instead of using ctx.runQuery or helper functions) is problematic because:
- Arguments and return values aren't validated despite validators being defined
- Functions lack isolation and atomicity that they would normally have
- When using custom functions (like triggers), it can cause deadlocks or other issues
The Convex team is working on adding warnings for this pattern, but ESLint can help catch it during development.
Recommended Approach
Instead of directly calling Convex functions, you should:
- Extract shared logic into helper functions in your model directory
- Use
ctx.runQuery,ctx.runMutation, orctx.runActionwhen you need to call another Convex function (though this has more overhead)
Here's an example of the recommended pattern using helper functions:
// In model/conversations.ts
export async function listMessages(ctx, { conversationId }) {
// Implementation here
}
// In your Convex function file
import * as Conversations from './model/conversations';
export const listMessages = internalQuery({
args: {
conversationId: v.id("conversations"),
},
handler: async (ctx, { conversationId }) => {
return Conversations.listMessages(ctx, { conversationId });
},
});
Understanding > Best Practices > Use helper functions