convex-expert-2025

📁 ghostspeak/ghostspeak 📅 6 days ago
1
总安装量
1
周安装量
#48290
全站排名
安装命令
npx skills add https://github.com/ghostspeak/ghostspeak --skill convex-expert-2025

Agent 安装分布

amp 1
opencode 1
kimi-cli 1
codex 1
github-copilot 1
gemini-cli 1

Skill 文档

Convex Expert Guide – December 2025

What is Convex?

Convex is a full-stack TypeScript backend platform with:

  • Reactive database – Real-time subscriptions, automatic cache invalidation
  • Serverless functions – Queries, mutations, actions with TypeScript
  • ACID transactions – Every mutation is a transaction, automatic conflict resolution
  • Type safety – End-to-end types from schema to React hooks
  • Built-in features – Auth, file storage, scheduling, vector search, workflows

Function Types

Type Purpose Database External APIs Deterministic
query Read data ✅ Read ❌ ✅ Required
mutation Write data ✅ Read/Write ❌ ✅ Required
action Side effects Via runQuery/runMutation ✅ ❌
httpAction HTTP endpoints Via ctx ✅ ❌

Quick Patterns

Schema Definition

// convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  users: defineTable({
    name: v.string(),
    email: v.string(),
    role: v.union(v.literal("admin"), v.literal("user")),
    profileId: v.optional(v.id("profiles")),
  })
    .index("by_email", ["email"])
    .index("by_role", ["role"]),

  messages: defineTable({
    authorId: v.id("users"),
    body: v.string(),
    channel: v.string(),
  })
    .index("by_channel", ["channel"])
    .searchIndex("search_body", { searchField: "body" }),
});

Query

// convex/messages.ts
import { query } from "./_generated/server";
import { v } from "convex/values";

export const list = query({
  args: { channel: v.string(), limit: v.optional(v.number()) },
  handler: async (ctx, args) => {
    return await ctx.db
      .query("messages")
      .withIndex("by_channel", (q) => q.eq("channel", args.channel))
      .order("desc")
      .take(args.limit ?? 50);
  },
});

Mutation

// convex/messages.ts
import { mutation } from "./_generated/server";
import { v } from "convex/values";

export const send = mutation({
  args: { body: v.string(), channel: v.string() },
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new Error("Unauthorized");

    return await ctx.db.insert("messages", {
      authorId: identity.subject,
      body: args.body,
      channel: args.channel,
    });
  },
});

Action

// convex/ai.ts
import { action } from "./_generated/server";
import { v } from "convex/values";
import { api, internal } from "./_generated/api";

export const generateResponse = action({
  args: { prompt: v.string(), threadId: v.id("threads") },
  handler: async (ctx, args) => {
    // Call external API
    const response = await fetch("https://api.openai.com/v1/chat/completions", {
      method: "POST",
      headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` },
      body: JSON.stringify({ model: "gpt-4", messages: [{ role: "user", content: args.prompt }] }),
    });
    const data = await response.json();
    
    // Write result via mutation
    await ctx.runMutation(internal.messages.saveAIResponse, {
      threadId: args.threadId,
      content: data.choices[0].message.content,
    });
  },
});

React Integration

// app/page.tsx
"use client";
import { useQuery, useMutation } from "convex/react";
import { api } from "../convex/_generated/api";

export default function Chat() {
  const messages = useQuery(api.messages.list, { channel: "general" });
  const sendMessage = useMutation(api.messages.send);

  if (messages === undefined) return <div>Loading...</div>;

  return (
    <div>
      {messages.map((msg) => <p key={msg._id}>{msg.body}</p>)}
      <button onClick={() => sendMessage({ body: "Hello!", channel: "general" })}>
        Send
      </button>
    </div>
  );
}

Reference Files

Load based on task:

Core Concepts

Reactivity

  • useQuery creates a WebSocket subscription
  • UI updates automatically when data changes
  • No manual cache invalidation needed
  • All subscriptions update atomically

Determinism

  • Queries and mutations must be deterministic
  • No Math.random(), Date.now(), or fetch allowed
  • Use _creationTime instead of Date.now()
  • Actions are the escape hatch for non-deterministic work

Transactions

  • Every mutation is an ACID transaction
  • Automatic optimistic concurrency control
  • Retries on conflicts
  • No BEGIN/COMMIT needed

Type Safety

  • Schema generates TypeScript types
  • Doc<"tableName"> for document types
  • Id<"tableName"> for ID types
  • End-to-end type checking from schema to React

Validator Reference

Validator TypeScript Type Example
v.string() string "hello"
v.number() number 42
v.boolean() boolean true
v.null() null null
v.id("table") Id<"table"> Document ID
v.array(v.string()) string[] ["a", "b"]
v.object({...}) {...} { name: "x" }
v.optional(v.X()) X | undefined Optional field
v.union(v.X(), v.Y()) X | Y Union type
v.literal("x") "x" Exact value
v.any() any Any value
v.bytes() ArrayBuffer Binary data
v.record(k, v) Record<K, V> Key-value map

Decision Framework

When to Use Each Function Type

Need to read data?
├─ Yes → query
│   └─ Need real-time updates? → useQuery (React)
│   └─ One-time fetch? → fetchQuery (Server)
└─ No
    └─ Need to write data?
        ├─ Yes → mutation
        │   └─ Also need external API? → mutation + scheduler.runAfter(0, action)
        └─ No
            └─ Need external API? → action
                └─ Need durability? → Workflow component

Internal vs Public Functions

Use Case Function Type
Client can call query, mutation, action
Backend only internalQuery, internalMutation, internalAction
Scheduled work Internal functions
Security-sensitive Internal functions

Project Structure

my-app/
├── convex/
│   ├── _generated/           # Auto-generated (don't edit)
│   │   ├── api.d.ts
│   │   ├── api.js
│   │   ├── dataModel.d.ts
│   │   └── server.d.ts
│   ├── schema.ts             # Database schema
│   ├── auth.ts               # Auth configuration
│   ├── users.ts              # User functions
│   ├── messages.ts           # Message functions
│   ├── crons.ts              # Scheduled jobs
│   ├── http.ts               # HTTP endpoints
│   └── model/                # Business logic (recommended)
│       ├── users.ts
│       └── messages.ts
├── app/                      # Next.js app
│   ├── ConvexClientProvider.tsx
│   └── page.tsx
└── .env.local
    └── NEXT_PUBLIC_CONVEX_URL=...

CLI Commands

# Initialize Convex in project
npx convex init

# Start development server (watches for changes)
npx convex dev

# Deploy to production
npx convex deploy

# Open dashboard
npx convex dashboard

# Run a function manually
npx convex run messages:list '{"channel": "general"}'

# Import data
npx convex import --table messages data.json

# Export data
npx convex export --path ./backup

Environment Variables

# .env.local (development)
NEXT_PUBLIC_CONVEX_URL=https://your-project.convex.cloud

# Set in Convex dashboard for production
OPENAI_API_KEY=sk-...
CLERK_SECRET_KEY=sk_...

Access in functions:

const apiKey = process.env.OPENAI_API_KEY;