mcpField
K
kitcn

kitcn

github.com/udecode开发45264 次安装2 周前更新
代码仓库

关于 kitcn

这是一项面向已配置 kitcn 应用交付功能的编码技能,提供 schema、cRPC 程序、界面、授权和测试方面的指导,并将初始化及专项任务引导至独立参考文档。

主要功能

  1. 01Schema、索引、关系和触发器指导
  2. 02cRPC 程序模式
  3. 03授权与速率限制检查
  4. 04使用 TanStack Query 构建 React 界面
  5. 05有界且关注索引的数据访问
  6. 06精简且高价值的测试指导

适用场景

  1. 01为已配置的 kitcn 应用添加功能
  2. 02实现经过授权的数据查询与变更
  3. 03构建由 cRPC 支持的界面
  4. 04测试授权逻辑与副作用

安装

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

$npx skills add https://github.com/udecode/kitcn --skill kitcn

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

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/features/
aggregates.md
auth-admin.md
auth-organizations.md
auth-polar.md
auth.md
create-plugins.md
http.md
migrations.md
orm.md
ratelimit.md
react.md
scheduling.md
testing.md
references/setup/
auth.md
biome.md
doc-guidelines.md
expo.md
index.md
next.md
react.md
server.md
start.md

详细介绍

kitcn 核心技能(80% 路径)

在已配置好的 kitcn 应用中交付日常功能时,首先使用此文件。

  • 如果缺少设置、引导、环境、身份验证连接或项目结构镜像,请使用 references/setup/index.md(然后查阅相关设置文件)。
  • 如果任务较高级或较小众,请仅加载末尾列出的特定功能参考文档。

范围

包含:

  • 添加或更新架构表、索引、关系和触发器。
  • 实现 cRPC 过程(query、mutation、action、httpAction),并加入运行时身份验证和速率限制。
  • 使用 useCRPC() + TanStack Query 实现功能界面。
  • 编写精简且价值高的测试,覆盖身份验证、错误和副作用。 不包含:
  • 从零开始的设置、安装、环境配置和引导。
  • 完整深入研究插件(admin/organizations/polar)。
  • 内部包级别的对等测试。

技能约定

  1. 优先使用 ctx.orm 访问应用数据。
  2. 列表和读取路径应有边界限制,并考虑索引。
  3. 使用 cRPC 构建器和中间件;新功能代码应避免使用原始处理器对象。
  4. 对预期失败使用 CRPCError。
  5. 跨行不变量优先使用架构触发器;但如果触发器执行不稳定(例如 init/seed 卡住或写入路径递归),则将不变量维护移至显式变更辅助函数。
  6. 在服务端进行身份验证和速率限制检查。
  7. 过程间调用:使用生成的运行时辅助函数:在查询/变更中使用 create<Module>Handler(ctx),在操作/HTTP 路由中使用 create<Module>Caller(ctx),调用操作过程时使用 caller.actions.*,安排任务时使用 caller.schedule.*。切勿直接为模块过程调用 ctx.runQuery/ctx.runMutation/ctx.runAction。

快捷模式(tRPC + Drizzle 思维模型)

默认假设:

  • cRPC 的行为类似 tRPC(构建器链 + 中间件 + TanStack 选项)。
  • ORM 的行为类似 Drizzle(架构、关系、findMany/findFirst、insert/update/delete)。 只需记住以下与之不一致的差异:
  1. 过程输入的根节点必须是 z.object(...)(不能使用原始类型的根参数)。
  2. 不支持 z.void() 输出;对于无返回值的变更,省略 .output(...)。
  3. .output(...) 会原样解析处理器的值,不会替换任何内容:处理器必须返回架构的输入类型,因此 z.string().nullable() 需要显式返回 null(?? null),不能返回 undefined。将缺失值建模为 .nullable(),绝不要使用顶层 .optional()——Convex 会将 undefined 转换为 null,且无法表达顶层可选性,因此 .output(z.string().optional()) 会发布 v.string(),并且只要处理器返回 undefined,部署就会因返回 null 而失败。对象内部使用 .optional() 没问题。zCustomQuery/zCustomMutation/zCustomAction 上的低级 returns: 选项有所不同——它会在解析前将 undefined 替换为 null。
  4. 多个 .input(...) 调用会合并输入形状。
  5. .paginated({ limit, item }) 必须放在 .query() 之前,并添加 input.cursor、input.endCursor 和 input.limit;将这三者全部传给 ORM 游标查询,以确保实时拆分保持有界。
  6. 元数据会通过代码生成写入 @convex/api 的叶节点(api.namespace.fn.meta),因此绝不要在 .meta(...) 中放置密钥;链式调用 .meta(...) 会进行浅层合并,并支持 defaultMeta。
  7. 身份验证元数据会驱动客户端行为:auth: "optional" 会等待身份验证加载后再运行,auth: "required" 会等待加载,然后在用户未登录时跳过。
  8. ctx.orm 会强制执行约束 + RLS;ctx.db 会绕过这些限制。
  9. 非分页的 findMany() 必须明确限制大小(使用 limit、游标模式、架构 defaultLimit 或显式 allowFullScan)。
  10. 谓词 where 必须显式使用 .withIndex(...);不会隐式回退到全表扫描。
  11. 游标分页使用第一个 orderBy 字段;为该字段建立索引,以确保分页稳定。
  12. maxScan 仅适用于游标模式;allowFullScan 用于显式启用非游标全表扫描。
  13. 字符串运算符、columns 投影和多对多关系子筛选可能在获取数据后执行;应尽早限制结果大小。
  14. 搜索模式按相关性排序,不支持 orderBy;向量模式限制更严格(不支持游标、偏移、顶层 where/order)。
  15. 不带 where 的更新/删除会抛出异常,除非使用 allowFullScan()。
  16. count()、aggregate() 和 groupBy() 需要匹配的 aggregateIndex。使用 groupBy({ by, _count, _sum }),而不是多次调用 .count(),或使用 findMany 再通过手动 JS 分组。by 中的每个字段都必须在 where 中受到有限值约束(eq/in/isNull)。参见 references/features/aggregates.md。
  17. cRPC React 查询默认是实时的(subscribe: true);对于这些订阅路径,绝不要使用 queryClient.invalidateQueries。
  18. 在 RSC 中,prefetch 会对客户端进行水合,caller 仅供服务器使用且不会进行水合,preloadQuery 会进行水合,但如果同时在客户端渲染,可能导致拆分所有权过期。
  19. Better Auth Next.js 快捷方式是 convexBetterAuth(...);通用的仅限服务器端快捷方式是 createCallerFactory(...)。
  20. 对于 kitcn 身份验证客户端路径,使用 createAuthMutations(authClient) 封装器,以便在登出前取消订阅身份验证查询。原始 Convex 预设保留更精简的普通 authClient。
  21. 绝不要直接使用 ctx.runQuery/ctx.runMutation/ctx.runAction 进行模块间调用。请使用 convex/functions/generated/<module>.runtime 中生成的运行时辅助函数。
  22. create<Module>Handler(ctx) 是查询/变更中的默认方式:零开销,仅接受查询/变更 ctx,且无需冗余验证或中间件。
  23. create<Module>Caller(ctx) 用于操作和 HTTP 路由。操作过程位于 caller.actions.* 下;调度位于 caller.schedule.now|after|at|cancel 下。仅在真正的 ActionCtx 回调中使用 requireActionCtx(ctx);当变更或操作上下文都可能执行调度时,使用 requireSchedulerCtx(ctx)。每个 caller/handler 都会急切加载其模块,因此应拆分大型模块。
  24. API 类型(Api、ApiInputs、ApiOutputs、Select、Insert、TableName)从 @convex/api 导入——无需手动使用 inferApiInputs<typeof api>。
  25. HTTP 路由器必须导出为 httpRouter(而不是 appRouter),以便代码生成。
  26. 服务端接线导入来自 convex/functions/generated/ 目录:从 generated/auth 导入 getAuth、defineAuth;从 generated/server 导入 initCRPC、QueryCtx、MutationCtx、OrmCtx;从 generated/<module>.runtime 导入 create<Module>Caller、create<Module>Handler。无需手动创建 convex/lib/orm.ts。
  27. defineAuth(() => ({ ...options, triggers })) 会取代拆分的 getAuthOptions + authTriggers。触发器回调以文档为先:beforeCreate(data)、onCreate(doc)、onUpdate(newDoc, oldDoc)——首个参数不是 ctx。
  28. 内部身份验证函数位于 internal.generated.*(而不是 internal.auth.*)。
  29. 异步变更批处理是默认行为(由代码生成接线)。可按调用进行自定义:execute({ batchSize, delayMs })。选择同步模式:execute({ mode: 'sync' }) 或 defineSchema(..., { defaults: { mutationExecutionMode: 'sync' } })。相关默认值:mutationBatchSize、mutationLeafBatchSize、mutationMaxRows、mutationScheduleCallCap。
  30. 多态联合以架构为先:在 convexTable(...) 中使用 actionType: discriminator({ variants, as? })。查询配置不包含 polymorphic 选项。写入保持扁平;读取会合成嵌套的 details(或自定义别名)。在判别字段表中使用 withVariants: true,可自动加载所有 one() 关系。
  31. 默认不要在应用/插件代码中添加手动 ORM 变更批处理循环。Convex 运行时批处理已能处理变更执行。优先使用集合式删除/更新,而不是逐行循环。仅在批处理外部副作用(例如 Resend API 调用)或执行有界清理时,才添加显式分块。

目录边界

在以下任务需要时使用 references/setup/:

  1. 设置项目/文件结构 → setup/index.md + setup/server.md
  2. 初始化身份验证 → setup/auth.md
  3. 连接客户端/提供方 → setup/react.md
  4. 特定框架的设置 → setup/next.md 或 setup/start.md 要完整复现模板:从 setup/index.md 开始,然后加载相关设置文件,接着加载选定的功能参考文档。

首轮功能需求梳理(编辑前完成)

先确定以下事项:

  1. 每个端点的身份验证级别:public / optionalAuth / auth / private。
  2. 数据不变量:写入后必须始终满足什么条件?
  3. 查询形态:列表、详情、加载关联数据、搜索或流式组合。
  4. 分页模式:偏移量、游标、无限滚动。
  5. 副作用:触发器、计划函数还是内联变更。
  6. UI 消费方式:仅客户端 Hook、RSC 预取,还是仅服务器调用方。
  7. 风险路径:未授权、禁止访问、未找到、冲突、速率限制。

标准文件目标

典型功能会涉及:

  • convex/functions/schema.ts
  • convex/functions/<feature>.ts
  • convex/lib/crpc.ts(仅在中间件/过程构建器发生变更时)
  • src/lib/convex/crpc.tsx(仅在 cRPC 上下文/元数据连接发生变更时)
  • src/** 功能 UI 文件
  • HTTP 端点对应的 convex/functions/http.ts 或 convex/routers/**
  • 必要时使用 convex/functions/crons.ts 或计划处理程序

端到端构建顺序(默认)

  1. Schema + 索引 + 关联关系。
  2. 用于跨行不变量的触发器 Hook(或者在触发器路径不稳定时,显式在变更中同步)。
  3. 严格校验输入/输出的过程 + 身份验证 + 速率限制。
  4. 使用 cRPC 选项的 React Hook(查询/变更/无限滚动)。
  5. 可选:HTTP 路由、计划 Hook。
  6. 测试身份验证/错误/触发器行为。

核心模式

1)Schema + 关联关系 + 触发器
import {
  convexTable,
  defineSchema,
  id,
  integer,
  index,
  text,
  timestamp,
} from "kitcn/orm";

export const project = convexTable(
  "project",
  {
    name: text().notNull(),
    ownerId: id("user").notNull(),
    updatedAt: timestamp()
      .notNull()
      .defaultNow()
      .$onUpdateFn(() => new Date()),
  },
  (t) => [index("ownerId_updatedAt").on(t.ownerId, t.updatedAt)]
);

export const task = convexTable(
  "task",
  {
    projectId: id("project").notNull(),
    title: text().notNull(),
    status: text().notNull().default("open"),
    updatedAt: timestamp()
      .notNull()
      .defaultNow()
      .$onUpdateFn(() => new Date()),
  },
  (t) => [index("projectId_updatedAt").on(t.projectId, t.updatedAt)]
);

export default defineSchema({ project, task })
  .relations((r) => ({
    project: {
      tasks: r.many.task(),
    },
    task: {
      project: r.one.project({ from: r.task.projectId, to: r.project.id }),
    },
  }))
  .triggers({
    task: {
      change: async (change, ctx) => {
        const projectId = change.newDoc?.projectId ?? change.oldDoc?.projectId;
        if (!projectId) return;
        const open = await ctx.orm.query.task.findMany({
          where: { projectId, status: "open" },
          columns: { id: true },
          limit: 500,
        });
        await ctx.orm.update(project).set({ openTaskCount: open.length });
      },
    },
  });

重要的 Schema 规则:

  1. 为用于筛选/排序/搜索的字段建立索引。
  2. many() 关联路径需要子表外键索引。
  3. 触发器逻辑必须有明确边界且不可递归。
  4. 使用表默认值,确保写入行为一致。
  5. ORM/查询的完整边缘情况请参阅 references/features/orm.md。
2)过程构建器 + 中间件
import { getSession } from "kitcn/auth";
import { CRPCError } from "kitcn/server";
import { initCRPC, type QueryCtx } from "../functions/generated/server";

const c = initCRPC
  .meta<{
    auth?: "optional" | "required";
    role?: "admin";
    ratelimit?: string;
  }>()
  .create();

function requireAuth<T>(user: T | null): T {
  if (!user) {
    throw new CRPCError({ code: "UNAUTHORIZED", message: "Not authenticated" });
  }
  return user;
}
async function getSessionUser(ctx: QueryCtx) {
  const session = await getSession(ctx);
  if (!session) return null;
  return await ctx.orm.query.user.findFirst({
    where: { id: { eq: session.userId } },
  });
}

export const publicQuery = c.query.meta({ auth: "optional" });
export const authQuery = c.query
  .meta({ auth: "required" })
  .use(async ({ ctx, next }) => {
    const user = requireAuth(await getSessionUser(ctx));
    return next({ ctx: { ...ctx, user, userId: user.id } });
  });
export const authMutation = c.mutation
  .meta({ auth: "optional" })
  .use(async ({ ctx, next }) => {
    const user = await getSessionUser(ctx);
    return next({
      ctx: { ...ctx, user, userId: user?.id ?? null },
    });
  });

重要的构建器规则:

  1. 在 convex/lib/crpc.ts 中各构建一次 public、optional、auth 和 private 过程族。已验证身份的 action 构建器位于 convex/lib/crpc-action.ts,这是唯一导入 getAuth 的构建器模块。
  2. .meta(...) 可通过生成的 API 元数据被客户端访问。绝不可在其中放置机密信息。
  3. 中间件会接收仅服务器可见的 procedure 信息。当使用应用的 generated/server 辅助函数构建过程时,标准的 export const 查询、变更和 action 会根据文件路径 + 导出名称自动推断 module:function。仅在需要覆盖或处理特殊导出形式时,才使用 .name("module:function")。
  4. 在中间件中解析一次会话/用户。不要在每个过程中重复获取身份验证状态。 查询/变更中间件使用来自 kitcn/auth 的 getSession(ctx),它会直接读取会话行。不要在 convex/lib/crpc.ts 中使用 getAuth(ctx):它会将整个 Better Auth 定义及所有身份验证插件引入每个过程模块的静态导入闭包,而 Convex 不支持动态 import() 来规避这一点。仅在调用 auth.api.* 的模块中导入 getAuth,包括 convex/lib/crpc-action.ts、HTTP 路由和组织/管理员变更。
  5. 共享的 c.middleware() 链会在变更过程中保留变更写入器类型。如果中间件本身会执行写入,请将其声明为仅适用于变更的类型:c.middleware<MutationCtx>(...)。
  6. 更深入的身份验证/运行时边缘情况请参阅 references/setup/server.md 和 references/features/auth*.md。
3)查询 + 变更过程模板
import * as z from "zod";
import { eq } from "kitcn/orm";
import { CRPCError } from "kitcn/server";
import { authMutation, authQuery } from "../lib/crpc";
import { project } from "./schema";

export const listProjects = authQuery
  .paginated({ limit: z.number().min(1).max(50).default(20), item: project })
  .query(async ({ ctx, input }) =>
    ctx.orm.query.project.findMany({
      where: { ownerId: ctx.userId },
      orderBy: { updatedAt: "desc" },
      cursor: input.cursor,
      endCursor: input.endCursor,
      limit: input.limit,
    })
  );

export const renameProject = authMutation
  .input(z.object({ id: z.string(), name: z.string().min(1).max(120) }))
  .mutation(async ({ ctx, input }) => {
    const current = await ctx.orm.query.project.findFirst({
      where: { id: input.id, ownerId: ctx.userId },
      columns: { id: true },
    });
    if (!current) {
      throw new CRPCError({ code: "NOT_FOUND", message: "Project not found" });
    }
    await ctx.orm
      .update(project)
      .set({ name: input.name })
      .where(eq(project.id, current.id));
    return null;
  });

重要的过程规则:

  1. 根输入必须是 z.object(...)。
  2. 使用严格的 .input(...);仅在需要时添加 .output(...)。
  3. 对无返回值的变更省略 .output(...)。
  4. 使用默认的变更速率限制;仅对命名的桶覆盖添加 .meta({ ratelimit: ... })。
  5. 对预期结果抛出 CRPCError。
  6. 使用 limit、游标或 .paginated(...) 限制每个列表。
  7. 将高级查询构建器形态移至 references/features/orm.md。
3b)过程间组合

使用:

  1. 在查询/变更中使用 create<Module>Handler(ctx)。
  2. 在操作/HTTP 路由中使用 create<Module>Caller(ctx)。
  3. 对操作过程使用 caller.actions.*。
  4. 对计划过程使用 caller.schedule.*。
  5. 对模块过程绝不使用 ctx.runQuery / ctx.runMutation / ctx.runAction。
4)查询模式(选择正确的模式)
  1. 默认使用对象形式的 where。
  2. 仅当组合形式比对象形式更易读时,才使用回调形式的 where。
  3. 谓词/筛选回调必须先使用 .withIndex(...),并明确指定 limit/maxScan。
  4. 全文搜索使用 search: { index, query, filters },且不支持 orderBy。
  5. 仅当 orderBy 字段已建立索引时,游标分页才稳定。
  6. 高级模式(pageByKey、向量搜索、管道、聚合索引)位于 references/features/orm.md。
5)变更模式(最常用)
  1. 如果调用方需要新建的 ID,在插入操作上使用 .returning(...)。
  2. 每条更新/删除路径都必须有明确的 where(...)。
  3. 使用 unsetToken 清除可选列。
  4. 异步变更执行是默认方式;仅在需要原子性的一次性行为时使用 .execute({ mode: "sync" })。
  5. 优先使用基于集合的删除/更新。仅针对外部副作用或有界清理添加分块处理。
  6. Upsert、冲突处理、变更批处理和架构扩展边缘情况位于 references/features/orm.md。
6)错误模型

始终按照以下映射处理:

  1. BAD_REQUEST:输入无效或业务前置条件不满足。
  2. UNAUTHORIZED:没有会话。
  3. FORBIDDEN:会话存在,但缺少权限。
  4. NOT_FOUND:资源缺失或无法访问。
  5. CONFLICT:写入重复或冲突。
  6. TOO_MANY_REQUESTS:速率限制。
  7. INTERNAL_SERVER_ERROR:仅用于意外故障。cRPC 在 .output(...) 解析失败时也会抛出此错误,错误消息为 Output validation failed,并在 error.data.ZodError 中包含已清理的结构化 Zod 问题。自定义问题消息和字段保留在服务端,因为其中可能包含处理器输出。
  8. 当客户端需要冲突 ID 等领域元数据时,在 CRPCError 上添加少量自定义 data 载荷。在客户端通过 error.data 读取。

必需的测试:

  1. 未经身份验证时的拒绝
  2. 相关情况下的权限拒绝
  3. 资源缺失路径
  4. 相关情况下的冲突路径
  5. 相关情况下的写入速率受限路径
7)React Query 集成

前置条件(编写或使用 useCRPC() 代码路径前必须满足):

  1. 已通过引导设置生成导入(@convex/api)。
  2. Provider 链已挂载(CRPCProvider 位于 QueryClient 和 Convex Provider 流程中)。
  3. 如果缺少引导设置或 Provider 前置条件,请先停止功能开发并完成 references/setup/。
  4. 后端状态位于项目本地的 .convex/ 中,而非 ~/.convex。

useCRPC() 模式:const crpc = useCRPC(); const projects = useQuery(crpc.project.listProjects.queryOptions({ cursor: null, limit: 20 })); const createProject = useMutation(crpc.project.createProject.mutationOptions());

客户端默认行为及差异:

  1. 查询默认实时订阅(subscribe: true)。
  2. 对已订阅的 cRPC 查询路径,绝不要使用 queryClient.invalidateQueries。
  3. 仅对一次性获取使用 { subscribe: false };通过显式调用 refetch / fetchQuery 刷新这些数据。
  4. 使用 skipUnauth: true 避免因未授权而反复触发获取。
  5. 对于分页,使用 kitcn/react 中的 useInfiniteQuery。
  6. 缓存读取、写入和获取操作优先使用类型化的 queryKey(...) 辅助函数,而非手动设置键。
  7. 对于 kitcn 身份验证流程,优先使用 createAuthMutations(...) 封装(而非直接调用原始身份验证客户端),以避免登出竞态错误。原始 Convex 预设仍使用普通的身份验证客户端路径。
  8. 对于 mutation 提示,优先使用 error.data?.message,而非 error.message;data.message 是简洁的 CRPCError 负载。
  9. 优先使用一个全局的 QueryClient mutation onError 提示,并通过 mutation.meta.errorMessage / skipErrorToast 配置,而非在每个组件中重复编写 onError。
  10. 完整的客户端 / RSC 深度说明见 references/features/react.md。
8)RSC 模式(Next.js)

针对每种用例选择一种:

  1. prefetch(...)(首选):非阻塞、已水合,数据由客户端负责。
  2. caller.*:仅限服务端的阻塞逻辑(重定向 / 身份验证检查),不进行水合。
  3. preloadQuery(...):服务端需要立即获取数据时使用,阻塞且会进行水合。

不要在服务端渲染 preloadQuery 结果,又在客户端为同一数据路径再次渲染。

  1. HydrateClient 必须包裹所有使用预取查询的客户端组件。
  2. Next.js 专属设置及更深入的水合权衡说明见 references/setup/next.md 和 references/features/react.md。
9)HTTP 路由模式(功能需要 REST / Webhook 时)
import { createTaskCaller } from "../functions/generated/task.runtime";

export const createTaskRoute = authRoute
  .post("/api/projects/:projectId/tasks")
  .params(z.object({ projectId: z.string() }))
  .input(z.object({ title: z.string().min(1) }))
  .output(z.object({ id: z.string() }))
  .mutation(async ({ ctx, params, input }) => {
    const caller = createTaskCaller(ctx);
    const id = await caller.createFromHttp({
      projectId: params.projectId,
      title: input.title,
      userId: ctx.userId,
    });
    return { id };
  });

HTTP 专属规则:

  1. 对搜索参数使用 z.coerce.*。
  2. 在中间件 / procedure 中进行身份验证和权限检查。
  3. 对公开 / 高负载端点应用速率限制。
  4. 在执行任何副作用前验证 Webhook 签名。
  5. 使用 convex/lib/crpc.ts 中的 publicRoute / authRoute / optionalAuthRoute 构建器。
  6. 使用 router(...) 按功能对端点进行分组。
  7. 客户端调用必须以 { params, searchParams } 形式传递路径 / 查询参数;查询值为字符串。
  8. Webhook、流式传输和 Hono 专属模式见 references/features/http.md。
10)调度模式(如有需要)

示例:const caller = createTaskCaller(ctx); await caller.schedule.now.sendTaskCreated({ taskId: created.id, userId: ctx.userId }); await caller.schedule.at(input.sendAt).sendReminder({ taskId: input.taskId, userId: ctx.userId });

调度规则:

  1. 不会传递身份验证上下文;请显式传入用户 / 组织 ID。
  2. mutation 调度与 mutation 事务保持原子性。
  3. 需要取消时,请保存返回的任务 ID。
  4. 在 action 中进行调度,与 action 失败不具备原子性。
  5. Cron 调度使用 UTC 运行。
  6. 只有必须调度非 procedure 的 internal.* 函数时,才直接使用 ctx.scheduler.*。
  7. Cron 表达式和运维细节见 references/features/scheduling.md。
11) 测试基线(高信号)

最小功能测试集:

  1. 成功路径查询/变更
  2. 未经身份验证时拒绝(UNAUTHORIZED)
  3. 权限/所有权检查未通过时拒绝(相关情况下使用 FORBIDDEN)
  4. 资源不存在(NOT_FOUND)
  5. 断言触发的副作用
  6. 如果功能会调度任务,则断言调度结果
  7. 检查资源不存在时,应使用真实 ID 或非 ID 查找键(slug/name/email),而非虚构 ID
  8. 完整的测试指南见 references/features/testing.md。
  9. 如果 Convex 引导流程阻碍集成测试,则提取纯守卫函数/辅助函数,并在引导流程可用后保留一个冒烟集成测试。

性能与安全检查清单

完成某项功能前:

  1. 每个列表查询都有限定范围(limit/游标)。
  2. 筛选条件/排序与索引一致。
  3. 开销较大的查询后逻辑使用预先缩小范围的索引路径。
  4. 变更使用有针对性的 where,避免意外全表扫描。
  5. 触发逻辑有界且幂等,并避免来回触发的循环。
  6. 错误代码明确且使用恰当。
  7. 面向用户的写入操作包含速率限制元数据。
  8. 测试涵盖身份验证、资源不存在和副作用。
  9. 依赖 ORM 约束/RLS 的路径不使用 ctx.db。
  10. 分页端点使用 .paginated(...) + ORM 游标流程(而非临时封装)。
  11. 对任何谓词/类似全表扫描的路径,都要显式使用 .withIndex(...) + 上限(limit/maxScan)。
  12. 绝不使用 @ts-nocheck,不降低全局 lint 规则的要求,修改过的文件中不得遗留未解决的 lint 警告。

常见错误(及修复方法)

错误做法正确模式
为新功能过程使用原始 Convex 处理器使用 cRPC 构建器(publicQuery、authMutation 等)
在多个 mutation 中重复写入时副作用使用 Schema 触发器;如果触发器路径不安全,则使用一个集中的 mutation 端同步辅助函数
列表/搜索缺少边界添加 limit + 游标/分页
将 orderBy 写成对象数组使用对象形式:orderBy: { updatedAt: "desc" }
对策略敏感的读取使用 ctx.db使用 ctx.orm(RLS/约束路径)
对预期结果抛出通用 Error使用带有明确代码的 CRPCError
直接使用 TanStack 原生钩子实现无限列表使用 kitcn/react 中的 useInfiniteQuery
根输入为基本类型(z.string())使用根 z.object(...) 输入 schema
使用 z.void() 表示不返回任何内容省略显式输出
在 .output(...nullable()) 下返回可能不存在的查询结果使用 ?? null 合并处理。.output(...) 不会为 undefined 替换任何内容
为无限端点手动封装分页使用 .paginated({ limit, item })
在测试中使用合成的 Convex ID("missing-id")使用已插入的 ID 或语义化查询键
已禁用聚合,但仍保留辅助函数/配置同时移除聚合辅助函数、defineTriggers 处理器和应用配置
将机密放入 .meta(...)保持元数据不含敏感信息(客户端可见)
直接使用 ctx.runQuery/ctx.runMutation/ctx.runAction在 queries/mutations 中使用 create<Module>Handler(ctx),在 actions/HTTP 中使用 create<Module>Caller(ctx),并配合 caller.actions.* / caller.schedule.*(来自 generated/<module>.runtime)
在 query/mutation 上下文中使用 createCaller使用 create<Module>Handler(ctx)——零开销,绕过冗余验证
添加 // @ts-nocheck 来绕过编译绝不要这样做;使用 references/setup/ 中的规范模式修复底层类型
放宽 lint 规则以通过检查保持基线 lint 配置;改为修复代码级警告/错误

参考资料升级索引(仅在需要时加载)

设置(每个项目一次):

  • references/setup/index.md:引导设置、环境、决策收集、关卡、检查清单、故障排除
  • references/setup/server.md:核心后端(架构、ORM、cRPC)+ 可选模块关卡
  • references/setup/auth.md:认证核心引导设置 + 插件设置
  • references/setup/react.md:客户端核心(QueryClient、提供程序、cRPC 上下文)
  • references/setup/next.md:Next.js App Router 设置
  • references/setup/start.md:TanStack Start 设置
  • references/setup/doc-guidelines.md:技能与文档同步约定

功能(每个会话,内容自成一体):

  • references/features/orm.md:完整 ORM API、约束、RLS、高级变更、筛选/搜索/组合/分页
  • references/features/react.md:完整客户端、RSC、水合、错误处理矩阵
  • references/features/http.md:类型化 REST 路由、Webhook、流式传输
  • references/features/scheduling.md:cron + 延迟任务模式
  • references/features/testing.md:更深入的测试场景
  • references/features/aggregates.md:聚合组件模式
  • references/features/migrations.md:内置在线数据迁移(defineMigration、CLI、部署、漂移)。在以下任务中加载:涉及数据回填、将可选字段强化为必填字段、字段重命名/移除、类型收窄,或 kitcn migrate CLI 命令。向后兼容的变更(新增可选字段、新增表、代码级默认值)无需加载。
  • references/features/create-plugins.md:规范的插件编写模式(拆分包入口、令牌配置、脚手架/锁文件/CLI 清单规则)。在创建或重构插件时加载。
  • references/features/ratelimit.md:ratelimit 运行时计量(分片预算分配、check() 与 limit()、快照转换、读取准确性、故障模式)。在调整 shards、读取剩余额度或排查意外拒绝时加载。对于普通的 ratelimit.middleware() 接线,无需加载,该内容由 setup/server.md 负责。
  • references/features/auth.md:完整 Better Auth 核心流程
  • references/features/auth-admin.md:管理插件详情
  • references/features/auth-organizations.md:组织/多租户插件详情

评论

支持 Markdown