关于 kitcn
这是一项面向已配置 kitcn 应用交付功能的编码技能,提供 schema、cRPC 程序、界面、授权和测试方面的指导,并将初始化及专项任务引导至独立参考文档。
主要功能
- 01Schema、索引、关系和触发器指导
- 02cRPC 程序模式
- 03授权与速率限制检查
- 04使用 TanStack Query 构建 React 界面
- 05有界且关注索引的数据访问
- 06精简且高价值的测试指导
适用场景
- 01为已配置的 kitcn 应用添加功能
- 02实现经过授权的数据查询与变更
- 03构建由 cRPC 支持的界面
- 04测试授权逻辑与副作用
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ udecode/ kitcn --skill kitcn安装后在 Claude Code 中输入/kitcn确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/features/aggregates.mdauth-admin.mdauth-organizations.mdauth-polar.mdauth.mdcreate-plugins.mdhttp.mdmigrations.mdorm.mdratelimit.mdreact.mdscheduling.mdtesting.mdreferences/setup/auth.mdbiome.mddoc-guidelines.mdexpo.mdindex.mdnext.mdreact.mdserver.mdstart.md详细介绍
kitcn 核心技能(80% 路径)
在已配置好的 kitcn 应用中交付日常功能时,首先使用此文件。
- 如果缺少设置、引导、环境、身份验证连接或项目结构镜像,请使用
references/setup/index.md(然后查阅相关设置文件)。 - 如果任务较高级或较小众,请仅加载末尾列出的特定功能参考文档。
范围
包含:
- 添加或更新架构表、索引、关系和触发器。
- 实现 cRPC 过程(
query、mutation、action、httpAction),并加入运行时身份验证和速率限制。 - 使用
useCRPC()+ TanStack Query 实现功能界面。 - 编写精简且价值高的测试,覆盖身份验证、错误和副作用。 不包含:
- 从零开始的设置、安装、环境配置和引导。
- 完整深入研究插件(admin/organizations/polar)。
- 内部包级别的对等测试。
技能约定
- 优先使用
ctx.orm访问应用数据。 - 列表和读取路径应有边界限制,并考虑索引。
- 使用 cRPC 构建器和中间件;新功能代码应避免使用原始处理器对象。
- 对预期失败使用
CRPCError。 - 跨行不变量优先使用架构触发器;但如果触发器执行不稳定(例如 init/seed 卡住或写入路径递归),则将不变量维护移至显式变更辅助函数。
- 在服务端进行身份验证和速率限制检查。
- 过程间调用:使用生成的运行时辅助函数:在查询/变更中使用
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)。 只需记住以下与之不一致的差异:
- 过程输入的根节点必须是
z.object(...)(不能使用原始类型的根参数)。 - 不支持
z.void()输出;对于无返回值的变更,省略.output(...)。 .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。- 多个
.input(...)调用会合并输入形状。 .paginated({ limit, item })必须放在.query()之前,并添加input.cursor、input.endCursor和input.limit;将这三者全部传给 ORM 游标查询,以确保实时拆分保持有界。- 元数据会通过代码生成写入
@convex/api的叶节点(api.namespace.fn.meta),因此绝不要在.meta(...)中放置密钥;链式调用.meta(...)会进行浅层合并,并支持defaultMeta。 - 身份验证元数据会驱动客户端行为:
auth: "optional"会等待身份验证加载后再运行,auth: "required"会等待加载,然后在用户未登录时跳过。 ctx.orm会强制执行约束 + RLS;ctx.db会绕过这些限制。- 非分页的
findMany()必须明确限制大小(使用limit、游标模式、架构defaultLimit或显式allowFullScan)。 - 谓词
where必须显式使用.withIndex(...);不会隐式回退到全表扫描。 - 游标分页使用第一个
orderBy字段;为该字段建立索引,以确保分页稳定。 maxScan仅适用于游标模式;allowFullScan用于显式启用非游标全表扫描。- 字符串运算符、
columns投影和多对多关系子筛选可能在获取数据后执行;应尽早限制结果大小。 - 搜索模式按相关性排序,不支持
orderBy;向量模式限制更严格(不支持游标、偏移、顶层where/order)。 - 不带
where的更新/删除会抛出异常,除非使用allowFullScan()。 count()、aggregate()和groupBy()需要匹配的aggregateIndex。使用groupBy({ by, _count, _sum }),而不是多次调用.count(),或使用findMany再通过手动 JS 分组。by中的每个字段都必须在where中受到有限值约束(eq/in/isNull)。参见references/features/aggregates.md。- cRPC React 查询默认是实时的(
subscribe: true);对于这些订阅路径,绝不要使用queryClient.invalidateQueries。 - 在 RSC 中,
prefetch会对客户端进行水合,caller仅供服务器使用且不会进行水合,preloadQuery会进行水合,但如果同时在客户端渲染,可能导致拆分所有权过期。 - Better Auth Next.js 快捷方式是
convexBetterAuth(...);通用的仅限服务器端快捷方式是createCallerFactory(...)。 - 对于 kitcn 身份验证客户端路径,使用
createAuthMutations(authClient)封装器,以便在登出前取消订阅身份验证查询。原始 Convex 预设保留更精简的普通authClient。 - 绝不要直接使用
ctx.runQuery/ctx.runMutation/ctx.runAction进行模块间调用。请使用convex/functions/generated/<module>.runtime中生成的运行时辅助函数。 create<Module>Handler(ctx)是查询/变更中的默认方式:零开销,仅接受查询/变更 ctx,且无需冗余验证或中间件。create<Module>Caller(ctx)用于操作和 HTTP 路由。操作过程位于caller.actions.*下;调度位于caller.schedule.now|after|at|cancel下。仅在真正的ActionCtx回调中使用requireActionCtx(ctx);当变更或操作上下文都可能执行调度时,使用requireSchedulerCtx(ctx)。每个 caller/handler 都会急切加载其模块,因此应拆分大型模块。- API 类型(
Api、ApiInputs、ApiOutputs、Select、Insert、TableName)从@convex/api导入——无需手动使用inferApiInputs<typeof api>。 - HTTP 路由器必须导出为
httpRouter(而不是appRouter),以便代码生成。 - 服务端接线导入来自
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。 defineAuth(() => ({ ...options, triggers }))会取代拆分的getAuthOptions+authTriggers。触发器回调以文档为先:beforeCreate(data)、onCreate(doc)、onUpdate(newDoc, oldDoc)——首个参数不是ctx。- 内部身份验证函数位于
internal.generated.*(而不是internal.auth.*)。 - 异步变更批处理是默认行为(由代码生成接线)。可按调用进行自定义:
execute({ batchSize, delayMs })。选择同步模式:execute({ mode: 'sync' })或defineSchema(..., { defaults: { mutationExecutionMode: 'sync' } })。相关默认值:mutationBatchSize、mutationLeafBatchSize、mutationMaxRows、mutationScheduleCallCap。 - 多态联合以架构为先:在
convexTable(...)中使用actionType: discriminator({ variants, as? })。查询配置不包含polymorphic选项。写入保持扁平;读取会合成嵌套的details(或自定义别名)。在判别字段表中使用withVariants: true,可自动加载所有one()关系。 - 默认不要在应用/插件代码中添加手动 ORM 变更批处理循环。Convex 运行时批处理已能处理变更执行。优先使用集合式删除/更新,而不是逐行循环。仅在批处理外部副作用(例如 Resend API 调用)或执行有界清理时,才添加显式分块。
目录边界
在以下任务需要时使用 references/setup/:
- 设置项目/文件结构 →
setup/index.md+setup/server.md - 初始化身份验证 →
setup/auth.md - 连接客户端/提供方 →
setup/react.md - 特定框架的设置 →
setup/next.md或setup/start.md要完整复现模板:从setup/index.md开始,然后加载相关设置文件,接着加载选定的功能参考文档。
首轮功能需求梳理(编辑前完成)
先确定以下事项:
- 每个端点的身份验证级别:
public/optionalAuth/auth/private。 - 数据不变量:写入后必须始终满足什么条件?
- 查询形态:列表、详情、加载关联数据、搜索或流式组合。
- 分页模式:偏移量、游标、无限滚动。
- 副作用:触发器、计划函数还是内联变更。
- UI 消费方式:仅客户端 Hook、RSC 预取,还是仅服务器调用方。
- 风险路径:未授权、禁止访问、未找到、冲突、速率限制。
标准文件目标
典型功能会涉及:
convex/functions/schema.tsconvex/functions/<feature>.tsconvex/lib/crpc.ts(仅在中间件/过程构建器发生变更时)src/lib/convex/crpc.tsx(仅在 cRPC 上下文/元数据连接发生变更时)src/**功能 UI 文件- HTTP 端点对应的
convex/functions/http.ts或convex/routers/** - 必要时使用
convex/functions/crons.ts或计划处理程序
端到端构建顺序(默认)
- Schema + 索引 + 关联关系。
- 用于跨行不变量的触发器 Hook(或者在触发器路径不稳定时,显式在变更中同步)。
- 严格校验输入/输出的过程 + 身份验证 + 速率限制。
- 使用 cRPC 选项的 React Hook(查询/变更/无限滚动)。
- 可选:HTTP 路由、计划 Hook。
- 测试身份验证/错误/触发器行为。
核心模式
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 规则:
- 为用于筛选/排序/搜索的字段建立索引。
many()关联路径需要子表外键索引。- 触发器逻辑必须有明确边界且不可递归。
- 使用表默认值,确保写入行为一致。
- 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 },
});
});
重要的构建器规则:
- 在
convex/lib/crpc.ts中各构建一次public、optional、auth和private过程族。已验证身份的 action 构建器位于convex/lib/crpc-action.ts,这是唯一导入getAuth的构建器模块。 .meta(...)可通过生成的 API 元数据被客户端访问。绝不可在其中放置机密信息。- 中间件会接收仅服务器可见的
procedure信息。当使用应用的generated/server辅助函数构建过程时,标准的export const查询、变更和 action 会根据文件路径 + 导出名称自动推断module:function。仅在需要覆盖或处理特殊导出形式时,才使用.name("module:function")。 - 在中间件中解析一次会话/用户。不要在每个过程中重复获取身份验证状态。
查询/变更中间件使用来自
kitcn/auth的getSession(ctx),它会直接读取会话行。不要在convex/lib/crpc.ts中使用getAuth(ctx):它会将整个 Better Auth 定义及所有身份验证插件引入每个过程模块的静态导入闭包,而 Convex 不支持动态import()来规避这一点。仅在调用auth.api.*的模块中导入getAuth,包括convex/lib/crpc-action.ts、HTTP 路由和组织/管理员变更。 - 共享的
c.middleware()链会在变更过程中保留变更写入器类型。如果中间件本身会执行写入,请将其声明为仅适用于变更的类型:c.middleware<MutationCtx>(...)。 - 更深入的身份验证/运行时边缘情况请参阅
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;
});
重要的过程规则:
- 根输入必须是
z.object(...)。 - 使用严格的
.input(...);仅在需要时添加.output(...)。 - 对无返回值的变更省略
.output(...)。 - 使用默认的变更速率限制;仅对命名的桶覆盖添加
.meta({ ratelimit: ... })。 - 对预期结果抛出
CRPCError。 - 使用
limit、游标或.paginated(...)限制每个列表。 - 将高级查询构建器形态移至
references/features/orm.md。
3b)过程间组合
使用:
- 在查询/变更中使用
create<Module>Handler(ctx)。 - 在操作/HTTP 路由中使用
create<Module>Caller(ctx)。 - 对操作过程使用
caller.actions.*。 - 对计划过程使用
caller.schedule.*。 - 对模块过程绝不使用
ctx.runQuery/ctx.runMutation/ctx.runAction。
4)查询模式(选择正确的模式)
- 默认使用对象形式的
where。 - 仅当组合形式比对象形式更易读时,才使用回调形式的
where。 - 谓词/筛选回调必须先使用
.withIndex(...),并明确指定limit/maxScan。 - 全文搜索使用
search: { index, query, filters },且不支持orderBy。 - 仅当
orderBy字段已建立索引时,游标分页才稳定。 - 高级模式(
pageByKey、向量搜索、管道、聚合索引)位于references/features/orm.md。
5)变更模式(最常用)
- 如果调用方需要新建的 ID,在插入操作上使用
.returning(...)。 - 每条更新/删除路径都必须有明确的
where(...)。 - 使用
unsetToken清除可选列。 - 异步变更执行是默认方式;仅在需要原子性的一次性行为时使用
.execute({ mode: "sync" })。 - 优先使用基于集合的删除/更新。仅针对外部副作用或有界清理添加分块处理。
- Upsert、冲突处理、变更批处理和架构扩展边缘情况位于
references/features/orm.md。
6)错误模型
始终按照以下映射处理:
BAD_REQUEST:输入无效或业务前置条件不满足。UNAUTHORIZED:没有会话。FORBIDDEN:会话存在,但缺少权限。NOT_FOUND:资源缺失或无法访问。CONFLICT:写入重复或冲突。TOO_MANY_REQUESTS:速率限制。INTERNAL_SERVER_ERROR:仅用于意外故障。cRPC 在.output(...)解析失败时也会抛出此错误,错误消息为Output validation failed,并在error.data.ZodError中包含已清理的结构化 Zod 问题。自定义问题消息和字段保留在服务端,因为其中可能包含处理器输出。- 当客户端需要冲突 ID 等领域元数据时,在
CRPCError上添加少量自定义data载荷。在客户端通过error.data读取。
必需的测试:
- 未经身份验证时的拒绝
- 相关情况下的权限拒绝
- 资源缺失路径
- 相关情况下的冲突路径
- 相关情况下的写入速率受限路径
7)React Query 集成
前置条件(编写或使用 useCRPC() 代码路径前必须满足):
- 已通过引导设置生成导入(
@convex/api)。 - Provider 链已挂载(
CRPCProvider位于 QueryClient 和 Convex Provider 流程中)。 - 如果缺少引导设置或 Provider 前置条件,请先停止功能开发并完成
references/setup/。 - 后端状态位于项目本地的
.convex/中,而非~/.convex。
useCRPC() 模式:const crpc = useCRPC(); const projects = useQuery(crpc.project.listProjects.queryOptions({ cursor: null, limit: 20 })); const createProject = useMutation(crpc.project.createProject.mutationOptions());
客户端默认行为及差异:
- 查询默认实时订阅(
subscribe: true)。 - 对已订阅的 cRPC 查询路径,绝不要使用
queryClient.invalidateQueries。 - 仅对一次性获取使用
{ subscribe: false };通过显式调用refetch/fetchQuery刷新这些数据。 - 使用
skipUnauth: true避免因未授权而反复触发获取。 - 对于分页,使用
kitcn/react中的useInfiniteQuery。 - 缓存读取、写入和获取操作优先使用类型化的
queryKey(...)辅助函数,而非手动设置键。 - 对于 kitcn 身份验证流程,优先使用
createAuthMutations(...)封装(而非直接调用原始身份验证客户端),以避免登出竞态错误。原始 Convex 预设仍使用普通的身份验证客户端路径。 - 对于 mutation 提示,优先使用
error.data?.message,而非error.message;data.message是简洁的CRPCError负载。 - 优先使用一个全局的
QueryClientmutationonError提示,并通过mutation.meta.errorMessage/skipErrorToast配置,而非在每个组件中重复编写onError。 - 完整的客户端 / RSC 深度说明见
references/features/react.md。
8)RSC 模式(Next.js)
针对每种用例选择一种:
prefetch(...)(首选):非阻塞、已水合,数据由客户端负责。caller.*:仅限服务端的阻塞逻辑(重定向 / 身份验证检查),不进行水合。preloadQuery(...):服务端需要立即获取数据时使用,阻塞且会进行水合。
不要在服务端渲染 preloadQuery 结果,又在客户端为同一数据路径再次渲染。
HydrateClient必须包裹所有使用预取查询的客户端组件。- 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 专属规则:
- 对搜索参数使用
z.coerce.*。 - 在中间件 / procedure 中进行身份验证和权限检查。
- 对公开 / 高负载端点应用速率限制。
- 在执行任何副作用前验证 Webhook 签名。
- 使用
convex/lib/crpc.ts中的publicRoute/authRoute/optionalAuthRoute构建器。 - 使用
router(...)按功能对端点进行分组。 - 客户端调用必须以
{ params, searchParams }形式传递路径 / 查询参数;查询值为字符串。 - 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 });
调度规则:
- 不会传递身份验证上下文;请显式传入用户 / 组织 ID。
- mutation 调度与 mutation 事务保持原子性。
- 需要取消时,请保存返回的任务 ID。
- 在 action 中进行调度,与 action 失败不具备原子性。
- Cron 调度使用 UTC 运行。
- 只有必须调度非 procedure 的
internal.*函数时,才直接使用ctx.scheduler.*。 - Cron 表达式和运维细节见
references/features/scheduling.md。
11) 测试基线(高信号)
最小功能测试集:
- 成功路径查询/变更
- 未经身份验证时拒绝(
UNAUTHORIZED) - 权限/所有权检查未通过时拒绝(相关情况下使用
FORBIDDEN) - 资源不存在(
NOT_FOUND) - 断言触发的副作用
- 如果功能会调度任务,则断言调度结果
- 检查资源不存在时,应使用真实 ID 或非 ID 查找键(slug/name/email),而非虚构 ID
- 完整的测试指南见
references/features/testing.md。 - 如果 Convex 引导流程阻碍集成测试,则提取纯守卫函数/辅助函数,并在引导流程可用后保留一个冒烟集成测试。
性能与安全检查清单
完成某项功能前:
- 每个列表查询都有限定范围(
limit/游标)。 - 筛选条件/排序与索引一致。
- 开销较大的查询后逻辑使用预先缩小范围的索引路径。
- 变更使用有针对性的
where,避免意外全表扫描。 - 触发逻辑有界且幂等,并避免来回触发的循环。
- 错误代码明确且使用恰当。
- 面向用户的写入操作包含速率限制元数据。
- 测试涵盖身份验证、资源不存在和副作用。
- 依赖 ORM 约束/RLS 的路径不使用
ctx.db。 - 分页端点使用
.paginated(...)+ ORM 游标流程(而非临时封装)。 - 对任何谓词/类似全表扫描的路径,都要显式使用
.withIndex(...)+ 上限(limit/maxScan)。 - 绝不使用
@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 migrateCLI 命令。向后兼容的变更(新增可选字段、新增表、代码级默认值)无需加载。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:组织/多租户插件详情