Claude Agent Skill · by Waynesutton

Convex Functions

Install Convex Functions skill for Claude Code from waynesutton/convexskills.

convexfunctionsqueriesmutationsactionshttp
Install
Terminal · npx
$npx skills add https://github.com/waynesutton/convexskills --skill convex-functions
Works with Paperclip

How Convex Functions fits into a Paperclip company.

Convex Functions drops into any Paperclip agent that handles convex and functions work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.

S
SaaS FactoryPaired

Pre-configured AI company — 18 agents, 18 skills, one-time purchase.

$27$59
Explore pack
Source file
SKILL.md458 lines
Expand
---name: convex-functionsdisplayName: Convex Functionsdescription: Writing queries, mutations, actions, and HTTP actions with proper argument validation, error handling, internal functions, and runtime considerationsversion: 1.0.0author: Convextags: [convex, functions, queries, mutations, actions, http]--- # Convex Functions Master Convex functions including queries, mutations, actions, and HTTP endpoints with proper validation, error handling, and runtime considerations. ## Code Quality All examples in this skill comply with @convex-dev/eslint-plugin rules: - Object syntax with `handler` property- Argument validators on all functions- Explicit table names in database operations See the Code Quality section in [convex-best-practices](../convex-best-practices/SKILL.md) for linting setup. ## Documentation Sources Before implementing, do not assume; fetch the latest documentation: - Primary: https://docs.convex.dev/functions- Query Functions: https://docs.convex.dev/functions/query-functions- Mutation Functions: https://docs.convex.dev/functions/mutation-functions- Actions: https://docs.convex.dev/functions/actions- HTTP Actions: https://docs.convex.dev/functions/http-actions- For broader context: https://docs.convex.dev/llms.txt ## Instructions ### Function Types Overview | Type        | Database Access          | External APIs | Caching       | Use Case              || ----------- | ------------------------ | ------------- | ------------- | --------------------- || Query       | Read-only                | No            | Yes, reactive | Fetching data         || Mutation    | Read/Write               | No            | No            | Modifying data        || Action      | Via runQuery/runMutation | Yes           | No            | External integrations || HTTP Action | Via runQuery/runMutation | Yes           | No            | Webhooks, APIs        | ### Queries Queries are reactive, cached, and read-only: ```typescriptimport { query } from "./_generated/server";import { v } from "convex/values"; export const getUser = query({  args: { userId: v.id("users") },  returns: v.union(    v.object({      _id: v.id("users"),      _creationTime: v.number(),      name: v.string(),      email: v.string(),    }),    v.null(),  ),  handler: async (ctx, args) => {    return await ctx.db.get("users", args.userId);  },}); // Query with indexexport const listUserTasks = query({  args: { userId: v.id("users") },  returns: v.array(    v.object({      _id: v.id("tasks"),      _creationTime: v.number(),      title: v.string(),      completed: v.boolean(),    }),  ),  handler: async (ctx, args) => {    return await ctx.db      .query("tasks")      .withIndex("by_user", (q) => q.eq("userId", args.userId))      .order("desc")      .collect();  },});``` ### Mutations Mutations modify the database and are transactional: ```typescriptimport { mutation } from "./_generated/server";import { v } from "convex/values";import { ConvexError } from "convex/values"; export const createTask = mutation({  args: {    title: v.string(),    userId: v.id("users"),  },  returns: v.id("tasks"),  handler: async (ctx, args) => {    // Validate user exists    const user = await ctx.db.get("users", args.userId);    if (!user) {      throw new ConvexError("User not found");    }     return await ctx.db.insert("tasks", {      title: args.title,      userId: args.userId,      completed: false,      createdAt: Date.now(),    });  },}); export const deleteTask = mutation({  args: { taskId: v.id("tasks") },  returns: v.null(),  handler: async (ctx, args) => {    await ctx.db.delete("tasks", args.taskId);    return null;  },});``` ### Actions Actions can call external APIs but have no direct database access: ```typescript"use node"; import { action } from "./_generated/server";import { v } from "convex/values";import { api, internal } from "./_generated/api"; export const sendEmail = action({  args: {    to: v.string(),    subject: v.string(),    body: v.string(),  },  returns: v.object({ success: v.boolean() }),  handler: async (ctx, args) => {    // Call external API    const response = await fetch("https://api.email.com/send", {      method: "POST",      headers: { "Content-Type": "application/json" },      body: JSON.stringify(args),    });     return { success: response.ok };  },}); // Action calling queries and mutationsexport const processOrder = action({  args: { orderId: v.id("orders") },  returns: v.null(),  handler: async (ctx, args) => {    // Read data via query    const order = await ctx.runQuery(api.orders.get, { orderId: args.orderId });     if (!order) {      throw new Error("Order not found");    }     // Call external payment API    const paymentResult = await processPayment(order);     // Update database via mutation    await ctx.runMutation(internal.orders.updateStatus, {      orderId: args.orderId,      status: paymentResult.success ? "paid" : "failed",    });     return null;  },});``` ### HTTP Actions HTTP actions handle webhooks and external requests: ```typescript// convex/http.tsimport { httpRouter } from "convex/server";import { httpAction } from "./_generated/server";import { api, internal } from "./_generated/api"; const http = httpRouter(); // Webhook endpointhttp.route({  path: "/webhooks/stripe",  method: "POST",  handler: httpAction(async (ctx, request) => {    const signature = request.headers.get("stripe-signature");    const body = await request.text();     // Verify webhook signature    if (!verifyStripeSignature(body, signature)) {      return new Response("Invalid signature", { status: 401 });    }     const event = JSON.parse(body);     // Process webhook    await ctx.runMutation(internal.payments.handleWebhook, {      eventType: event.type,      data: event.data,    });     return new Response("OK", { status: 200 });  }),}); // API endpointhttp.route({  path: "/api/users/:userId",  method: "GET",  handler: httpAction(async (ctx, request) => {    const url = new URL(request.url);    const userId = url.pathname.split("/").pop();     const user = await ctx.runQuery(api.users.get, {      userId: userId as Id<"users">,    });     if (!user) {      return new Response("Not found", { status: 404 });    }     return Response.json(user);  }),}); export default http;``` ### Internal Functions Use internal functions for sensitive operations: ```typescriptimport {  internalMutation,  internalQuery,  internalAction,} from "./_generated/server";import { v } from "convex/values"; // Only callable from other Convex functionsexport const _updateUserCredits = internalMutation({  args: {    userId: v.id("users"),    amount: v.number(),  },  returns: v.null(),  handler: async (ctx, args) => {    const user = await ctx.db.get("users", args.userId);    if (!user) return null;     await ctx.db.patch("users", args.userId, {      credits: (user.credits || 0) + args.amount,    });    return null;  },}); // Call internal function from actionexport const purchaseCredits = action({  args: { userId: v.id("users"), amount: v.number() },  returns: v.null(),  handler: async (ctx, args) => {    // Process payment externally    await processPayment(args.amount);     // Update credits via internal mutation    await ctx.runMutation(internal.users._updateUserCredits, {      userId: args.userId,      amount: args.amount,    });     return null;  },});``` ### Scheduling Functions Schedule functions to run later: ```typescriptimport { mutation, internalMutation } from "./_generated/server";import { v } from "convex/values";import { internal } from "./_generated/api"; export const scheduleReminder = mutation({  args: {    userId: v.id("users"),    message: v.string(),    delayMs: v.number(),  },  returns: v.id("_scheduled_functions"),  handler: async (ctx, args) => {    return await ctx.scheduler.runAfter(      args.delayMs,      internal.notifications.sendReminder,      { userId: args.userId, message: args.message },    );  },}); export const sendReminder = internalMutation({  args: {    userId: v.id("users"),    message: v.string(),  },  returns: v.null(),  handler: async (ctx, args) => {    await ctx.db.insert("notifications", {      userId: args.userId,      message: args.message,      sentAt: Date.now(),    });    return null;  },});``` ## Examples ### Complete Function File ```typescript// convex/messages.tsimport { query, mutation, internalMutation } from "./_generated/server";import { v } from "convex/values";import { ConvexError } from "convex/values";import { internal } from "./_generated/api"; const messageValidator = v.object({  _id: v.id("messages"),  _creationTime: v.number(),  channelId: v.id("channels"),  authorId: v.id("users"),  content: v.string(),  editedAt: v.optional(v.number()),}); // Public queryexport const list = query({  args: {    channelId: v.id("channels"),    limit: v.optional(v.number()),  },  returns: v.array(messageValidator),  handler: async (ctx, args) => {    const limit = args.limit ?? 50;    return await ctx.db      .query("messages")      .withIndex("by_channel", (q) => q.eq("channelId", args.channelId))      .order("desc")      .take(limit);  },}); // Public mutationexport const send = mutation({  args: {    channelId: v.id("channels"),    authorId: v.id("users"),    content: v.string(),  },  returns: v.id("messages"),  handler: async (ctx, args) => {    if (args.content.trim().length === 0) {      throw new ConvexError("Message cannot be empty");    }     const messageId = await ctx.db.insert("messages", {      channelId: args.channelId,      authorId: args.authorId,      content: args.content.trim(),    });     // Schedule notification    await ctx.scheduler.runAfter(0, internal.messages.notifySubscribers, {      channelId: args.channelId,      messageId,    });     return messageId;  },}); // Internal mutationexport const notifySubscribers = internalMutation({  args: {    channelId: v.id("channels"),    messageId: v.id("messages"),  },  returns: v.null(),  handler: async (ctx, args) => {    // Get channel subscribers and notify them    const subscribers = await ctx.db      .query("subscriptions")      .withIndex("by_channel", (q) => q.eq("channelId", args.channelId))      .collect();     for (const sub of subscribers) {      await ctx.db.insert("notifications", {        userId: sub.userId,        messageId: args.messageId,        read: false,      });    }    return null;  },});``` ## Best Practices - Never run `npx convex deploy` unless explicitly instructed- Never run any git commands unless explicitly instructed- Always define args and returns validators- Use queries for read operations (they are cached and reactive)- Use mutations for write operations (they are transactional)- Use actions only when calling external APIs- Use internal functions for sensitive operations- Add `"use node";` at the top of action files using Node.js APIs- Handle errors with ConvexError for user-facing messages ## Common Pitfalls 1. **Using actions for database operations** - Use queries/mutations instead2. **Calling external APIs from queries/mutations** - Use actions3. **Forgetting to add "use node"** - Required for Node.js APIs in actions4. **Missing return validators** - Always specify returns5. **Not using internal functions for sensitive logic** - Protect with internalMutation ## References - Convex Documentation: https://docs.convex.dev/- Convex LLMs.txt: https://docs.convex.dev/llms.txt- Functions Overview: https://docs.convex.dev/functions- Query Functions: https://docs.convex.dev/functions/query-functions- Mutation Functions: https://docs.convex.dev/functions/mutation-functions- Actions: https://docs.convex.dev/functions/actions