All skills
get-convex avatar

/convex-create-component

@7a6fcc6
by Convexget-convex/agent-skills62 stars
11

Builds reusable Convex components with isolated tables and app-facing APIs. Use for new components, reusable backend modules, integrations, or component boundary work.

Use this Skill: https://skilld.dev/gh/get-convex/agent-skills/convex-create-component

This session only. Nothing lands on disk.

referencesadvanced-patterns.md

≈964 tokens on demand. Your agent reads this file only when SKILL.md points to it.

Advanced Component Patterns

Additional patterns for Convex components that go beyond the basics covered in the main skill file.

Function Handles for callbacks

When the app needs to pass a callback function to the component, use function handles. This is common for components that run app-defined logic on a schedule or in a workflow.

// App side: create a handle and pass it to the component
import { createFunctionHandle } from "convex/server";

export const startJob = mutation({
  handler: async (ctx) => {
    const handle = await createFunctionHandle(internal.myModule.processItem);
    await ctx.runMutation(components.workpool.enqueue, {
      callback: handle,
    });
  },
});
// Component side: accept and invoke the handle
import { v } from "convex/values";
import type { FunctionHandle } from "convex/server";
import { mutation } from "./_generated/server.js";

export const enqueue = mutation({
  args: { callback: v.string() },
  handler: async (ctx, args) => {
    const handle = args.callback as FunctionHandle<"mutation">;
    await ctx.scheduler.runAfter(0, handle, {});
  },
});

Deriving validators from schema

Instead of manually repeating field types in return validators, extend the schema validator:

import { v } from "convex/values";
import schema from "./schema.js";

const vNotification = schema.doc("notifications").omit("userId").extend({
  user: v.string(),
});

export const getNotification = internalQuery({
  args: { id: schema.id("notifications") },
  returns: v.nullable(vNotification),
  handler: async (ctx) => {
    const notification = await ctx.db.get("notifications", args.id);
    if (!notification) return null;
    const { userId, ...rest } = notification;
    const user = await ctx.db.get("users", userId);
    return {
      ...rest,
      user: user?.name ?? "Unknown",
    };
  },
});

Static configuration with a globals table

A common pattern for component configuration is a single-document "globals" table:

// schema.ts
export default defineSchema({
  globals: defineTable({
    maxRetries: v.number(),
    webhookUrl: v.optional(v.string()),
  }),
  // ... other tables
});
// lib.ts
export const configure = mutation({
  args: { maxRetries: v.number(), webhookUrl: v.optional(v.string()) },
  returns: v.null(),
  handler: async (ctx, args) => {
    const existing = await ctx.db.query("globals").first();
    if (existing) {
      await ctx.db.patch(existing._id, args);
    } else {
      await ctx.db.insert("globals", args);
    }
    return null;
  },
});

Class-based client wrappers

For components with many functions or configuration options, a class-based client provides a cleaner API. This pattern is common in published components.

// src/client/index.ts
import type { GenericMutationCtx, GenericDataModel } from "convex/server";
import type { ComponentApi } from "../component/_generated/component.js";

type MutationCtx = Pick<GenericMutationCtx<GenericDataModel>, "runMutation">;

export class Notifications {
  constructor(
    private component: ComponentApi,
    private options?: { defaultChannel?: string },
  ) {}

  async send(ctx: MutationCtx, args: { userId: string; message: string }) {
    return await ctx.runMutation(this.component.lib.send, {
      ...args,
      channel: this.options?.defaultChannel ?? "default",
    });
  }
}
// App usage
import { Notifications } from "@convex-dev/notifications";
import { components } from "./_generated/api";

const notifications = new Notifications(components.notifications, {
  defaultChannel: "alerts",
});

export const send = mutation({
  args: { message: v.string() },
  handler: async (ctx, args) => {
    const userId = await getAuthUserId(ctx);
    await notifications.send(ctx, { userId, message: args.message });
  },
});

Source: SKILL.md on GitHub

No alerts16d4 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The skill is a development tool for building Convex components that follows security best practices, such as isolating authentication and environment variables from component logic. It utilizes official platform CLI tools and standard libraries for development workflows.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 7a6fcc6. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 days ago.

Activeupdated 4 months ago

README badge

README badge for get-convex/agent-skills/convex-create-component