| name | source-command-convex-function-creator |
| description | Create Convex functions (queries, mutations, actions) with proper validation, auth, and patterns for this project |
source-command-convex-function-creator
Use this skill when the user asks to run the migrated source command convex-function-creator.
Command Template
Convex Function Creator
Create Convex functions with proper validation, authentication, and error handling for this project.
Function Types
| Type | Purpose | Database Access | External APIs |
|---|
query | Read data | Yes (ctx.db) | No |
mutation | Write data | Yes (ctx.db) | No |
action | Side effects | No (use ctx.runQuery/ctx.runMutation) | Yes |
Query Template
import { v } from "convex/values";
import { query } from "./_generated/server";
import { authComponent } from "./auth";
export const list = query({
args: {
},
returns: v.array(
v.object({
}),
),
handler: async (ctx, args) => {
const user = await authComponent.getAuthUser(ctx);
if (!user) throw new Error("Not authenticated");
return await ctx.db
.query("tableName")
.withIndex("by_userId", (q) => q.eq("userId", user._id))
.collect();
},
});
Mutation Template
import { v } from "convex/values";
import { mutation } from "./_generated/server";
import { authComponent } from "./auth";
export const create = mutation({
args: {
title: v.string(),
},
returns: v.id("tableName"),
handler: async (ctx, args) => {
const user = await authComponent.getAuthUser(ctx);
if (!user) throw new Error("Not authenticated");
return await ctx.db.insert("tableName", {
userId: user._id,
title: args.title,
createdAt: Date.now(),
});
},
});
Action Template
"use node";
import { v } from "convex/values";
import { action } from "./_generated/server";
import { authComponent } from "./auth";
export const processExternal = action({
args: {
itemId: v.id("tableName"),
},
returns: v.null(),
handler: async (ctx, args) => {
const user = await authComponent.getAuthUser(ctx);
if (!user) throw new Error("Not authenticated");
const response = await fetch("https://api.example.com/...");
const data = await response.json();
await ctx.runMutation(internal.tableName.saveResult, {
itemId: args.itemId,
result: data,
});
return null;
},
});
Internal Function Template
Internal functions are only callable by other Convex functions (not from clients):
import { v } from "convex/values";
import { internalMutation, internalQuery } from "./_generated/server";
export const processItem = internalMutation({
args: { itemId: v.id("tableName"), data: v.string() },
returns: v.null(),
handler: async (ctx, args) => {
await ctx.db.patch(args.itemId, { processedData: args.data });
return null;
},
});
Required Components
Every function MUST have:
args validator - defines and validates input types
returns validator - defines return type
- Auth check -
authComponent.getAuthUser(ctx) for user-facing functions
- Ownership verification - check the user owns the resource before mutations
Validator Reference
v.string();
v.number();
v.boolean();
v.int64();
v.null();
v.id("tableName");
v.array(v.string());
v.object({ key: v.string() });
v.optional(v.string());
v.union(v.literal("a"), v.literal("b"));
v.record(v.string(), v.number());
Common Patterns
Paginated Query
import { paginationOptsValidator } from "convex/server";
export const listPaginated = query({
args: { paginationOpts: paginationOptsValidator },
handler: async (ctx, args) => {
return await ctx.db
.query("tableName")
.order("desc")
.paginate(args.paginationOpts);
},
});
Scheduling Work
await ctx.scheduler.runAfter(0, internal.tasks.processItem, { itemId });
await ctx.scheduler.runAt(futureTimestamp, internal.tasks.cleanup, {});
Batch Operations
import { asyncMap } from "convex-helpers";
const results = await asyncMap(ids, async (id) => {
return await ctx.db.get(id);
});
Rules
- Never use
Date.now() in queries (breaks reactivity)
- Never use
.filter() on db.query() - use .withIndex() instead
- Always
await every promise (ctx.db.patch, ctx.scheduler.runAfter, etc.)
- Add
"use node"; directive to action files using Node.js APIs
- Actions cannot access
ctx.db directly - use ctx.runQuery() / ctx.runMutation()