#eslint for calling mutation, query or aciton directly

4 messages · Page 1 of 1 (latest)

dense surge
#

eslint for calling mutation, query or aciton directly

sacred wrenBOT
#

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:

  1. Arguments and return values aren't validated despite validators being defined
  2. Functions lack isolation and atomicity that they would normally have
  3. 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:

  1. Extract shared logic into helper functions in your model directory
  2. Use ctx.runQuery, ctx.runMutation, or ctx.runAction when 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

This approach is more maintainable, secure, and follows Convex's recommended best practices.

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