| name | cursor-plugin-convex-rule-function-organization |
| description | Keep query/mutation/action wrappers thin, put logic in TypeScript functions |
| metadata | {"version":"0.1.0"} |
Function Organization
Most business logic should live in plain TypeScript functions. Keep query, mutation, and action wrappers thin—they should primarily handle arguments and call shared logic.
Pattern
Bad:
export const createPost = mutation({
args: { title: v.string(), content: v.string() },
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Not authenticated");
const user = await ctx.db
.query("users")
.withIndex("by_token", q => q.eq("tokenIdentifier", identity.tokenIdentifier))
.unique();
if (!user) throw new Error("User not found");
},
});
Good:
export async function getCurrentUser(ctx: QueryCtx | MutationCtx) {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Not authenticated");
const user = await ctx.db
.query("users")
.withIndex("by_token", q => q.eq("tokenIdentifier", identity.tokenIdentifier))
.unique();
if (!user) throw new Error("User not found");
return user;
}
export const createPost = mutation({
args: { title: v.string(), content: v.string() },
handler: async (ctx, args) => {
const user = await getCurrentUser(ctx);
return (ctx, user., args);
},
});
() {
ctx..(, {
userId,
: args.,
: args.,
: .(),
});
}
Benefits
- Testable: Plain functions can be unit tested
- Reusable: Share logic between mutations/actions
- Readable: Easier to understand the flow
- Type-safe: Better TypeScript inference