mcpField
C
convex

convex

github.com/mindrally开发288662 次安装3 周前更新
代码仓库

关于 convex

面向使用 Convex 和 TypeScript 构建后端的开发者,提供查询、变更、操作及实时数据同步的编码指南。内容也涵盖索引、HTTP 路由、定时任务和文件上传。

主要功能

  1. 01编写查询、变更和操作
  2. 02使用索引与参数校验
  3. 03配置 HTTP 路由和定时任务
  4. 04处理文件上传
  5. 05使用实时订阅

适用场景

  1. 01实现 Convex 数据查询与写入
  2. 02构建外部 API 调用和副作用
  3. 03设置 Webhook 与周期任务
  4. 04为客户端实现文件上传和实时更新

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/mindrally/skills --skill convex

安装后在 Claude Code 中输入/convex确认已加载。

详细介绍

Convex 开发指南

你精通 Convex 后端开发、TypeScript 和实时数据同步模式。

通用开发规范

代码风格与结构
  • 使用简洁的 TypeScript,采用函数声明、迭代器和模块
  • 使用描述性变量名,并包含辅助动词(例如,isLoading、hasError)
  • 按导出的组件、子组件、辅助函数和静态类型组织代码
  • 目录使用 dash-case,并采用命名导出
  • 优先使用接口而非类型;使用联合类型代替枚举
  • 使用函数组件和声明式 JSX 模式
错误处理
  • 在函数中尽早通过守卫子句处理错误
  • 适当地记录错误以便调试
  • 提供对用户友好的错误消息
  • 使用 Zod 进行表单验证
  • 在 React 组件中实现恰当的错误边界
UI 框架集成
  • 使用 Shadcn UI 和 Radix UI 作为组件基础
  • 使用 Tailwind CSS 编写样式,采用响应式、移动端优先的设计
  • 尽量减少 useClient、useEffect 和 useState 的使用
  • 在适用的场景中使用 React Server Components
  • 使用 Suspense 处理加载状态,并通过动态加载拆分代码

Convex 专用模式

查询

使用 query 构造函数组织查询:

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

export const getItems = query({
  args: {
    status: v.optional(v.string()),
  },
  handler: async (ctx, args) => {
    // ctx provides: db, storage, auth
    const identity = await ctx.auth.getUserIdentity();

    if (args.status) {
      return await ctx.db
        .query("items")
        .withIndex("by_status", (q) => q.eq("status", args.status))
        .collect();
    }

    return await ctx.db.query("items").collect();
  },
});

重要提示:优先使用 Convex 索引而非筛选器,以获得更好的性能。在 schema.ts 中使用 .index() 方法定义索引,然后通过 .withIndex() 查询。

突变

组织用于数据库写入的突变:

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

export const createItem = mutation({
  args: {
    title: v.string(),
    description: v.optional(v.string()),
  },
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) {
      throw new Error("Not authenticated");
    }

    return await ctx.db.insert("items", {
      title: args.title,
      description: args.description,
      userId: identity.subject,
      createdAt: Date.now(),
    });
  },
});
操作

使用操作进行外部 API 调用和副作用:

import { action } from "./_generated/server";
import { v } from "convex/values";

export const sendEmail = action({
  args: {
    to: v.string(),
    subject: v.string(),
    body: v.string(),
  },
  handler: async (ctx, args) => {
    // Actions can call external APIs
    const response = await fetch("https://api.email-service.com/send", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(args),
    });

    return response.ok;
  },
});
使用索引定义架构
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  items: defineTable({
    title: v.string(),
    description: v.optional(v.string()),
    status: v.string(),
    userId: v.string(),
    createdAt: v.number(),
  })
    .index("by_status", ["status"])
    .index("by_user", ["userId"])
    .index("by_user_and_status", ["userId", "status"]),
});
HTTP 路由器

为 Webhook 和外部集成定义 HTTP 路由:

import { httpRouter } from "convex/server";
import { httpAction } from "./_generated/server";

const http = httpRouter();

http.route({
  path: "/webhook",
  method: "POST",
  handler: httpAction(async (ctx, request) => {
    const body = await request.json();
    // Process webhook
    return new Response(JSON.stringify({ success: true }), {
      status: 200,
      headers: { "Content-Type": "application/json" },
    });
  }),
});

export default http;
计划任务

为定期任务实现 cron 作业:

import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";

const crons = cronJobs();

// Run every hour
crons.interval(
  "cleanup-old-items",
  { hours: 1 },
  internal.tasks.cleanupOldItems
);

// Run at specific time (daily at midnight UTC)
crons.monthly(
  "monthly-report",
  { day: 1, hourUTC: 0, minuteUTC: 0 },
  internal.reports.generateMonthlyReport
);

export default crons;
文件处理

文件上传分为三个步骤:

// 1. Generate upload URL (mutation)
export const generateUploadUrl = mutation(async (ctx) => {
  return await ctx.storage.generateUploadUrl();
});

// 2. Client POSTs file to the URL
// const uploadUrl = await generateUploadUrl();
// const response = await fetch(uploadUrl, { method: "POST", body: file });
// const { storageId } = await response.json();

// 3. Save storage ID to database (mutation)
export const saveFile = mutation({
  args: {
    storageId: v.id("_storage"),
    filename: v.string(),
  },
  handler: async (ctx, args) => {
    return await ctx.db.insert("files", {
      storageId: args.storageId,
      filename: args.filename,
    });
  },
});

最佳实践

  1. 始终为筛选或排序数据的查询使用索引
  2. 使用 Convex 验证器(v.string()、v.number() 等)验证参数
  3. 在需要身份验证的处理程序中尽早检查身份验证
  4. 对于不应向客户端公开的操作,使用内部函数
  5. 利用实时订阅——Convex 查询会在数据更改时自动更新
  6. 保持突变精简,专注于单一操作
  7. 使用操作处理副作用——切勿从查询或突变中调用外部 API
  8. 妥善处理错误,向用户提供恰当的错误消息

性能注意事项

  • 尽可能使用 .withIndex() 代替 .filter()
  • 使用 .paginate() 对大型结果集进行分页
  • 预期只返回单个结果时,使用 .first() 代替 .collect()
  • 对频繁访问的数据考虑进行数据反规范化
  • 使用 Convex 内置的缓存,避免自行实现缓存

评论

支持 Markdown