kitcn

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

kitcn Core Skill (80% Path)

kitcn核心技能(80%通用流程)

Use this file first for everyday feature delivery in an already configured kitcn app.
  • If setup/bootstrap/env/auth wiring or project structure mirroring is missing, use
    references/setup/index.md
    (then the relevant setup file).
  • If the task is advanced or niche, load only the specific feature reference listed at the end.
在已配置完成的kitcn应用中,日常功能开发请优先使用本文件。
  • 如果缺少搭建/初始化/环境/鉴权配置或项目结构镜像,请使用
    references/setup/index.md
    (之后再使用相关的搭建文件)。
  • 如果任务属于高级或小众场景,仅加载末尾列出的特定功能参考文档。

Scope

适用范围

In scope:
  • Add or update schema tables, indexes, relations, and triggers.
  • Implement cRPC procedures (
    query
    ,
    mutation
    ,
    action
    ,
    httpAction
    ) with runtime auth + rate limits.
  • Implement feature UI with
    useCRPC()
    + TanStack Query.
  • Add minimal high-value tests for auth, errors, and side effects. Out of scope:
  • Greenfield setup/install/env/bootstrap.
  • Full plugin deep-dives (admin/organizations/polar).
  • Internal package-level parity testing.
适用场景:
  • 添加或更新schema表、索引、关联关系和触发器。
  • 实现带有运行时鉴权+速率限制的cRPC过程(
    query
    mutation
    action
    httpAction
    )。
  • 使用
    useCRPC()
    + TanStack Query实现功能UI。
  • 为鉴权、错误处理和副作用添加高价值的极简测试。 不适用场景:
  • 全新项目的搭建/安装/环境/初始化。
  • 完整插件深度解析(admin/organizations/polar)。
  • 内部包级别的一致性测试。

Skill Contract

技能约定

  1. Favor
    ctx.orm
    for app data access.
  2. Keep list/read paths bounded and index-aware.
  3. Use cRPC builders and middleware; avoid raw handler objects for new feature code.
  4. Use
    CRPCError
    for expected failures.
  5. Prefer schema triggers for cross-row invariants, but move invariant maintenance to explicit mutation helpers if trigger execution is unstable (for example init/seed hangs or recursive write paths).
  6. Keep auth/rate-limit checks server-side.
  7. Inter-procedure calls:
    create<Module>Handler(ctx)
    in queries/mutations (zero overhead) unless validation is relevant,
    create<Module>Caller(ctx)
    in actions/HTTP routes. In action context use
    caller.actions.*
    for action procedures and
    caller.schedule.*
    for scheduling. Import from
    ./generated/<module>.runtime
    . Never call
    ctx.runQuery
    /
    ctx.runMutation
    /
    ctx.runAction
    directly for module procedures.
  1. 优先使用
    ctx.orm
    进行应用数据访问。
  2. 列表/读取路径需限制范围并考虑索引。
  3. 使用cRPC构建器和中间件;新功能代码避免使用原始处理器对象。
  4. 预期失败场景使用
    CRPCError
    抛出错误。
  5. 优先使用schema触发器维护跨行约束,但如果触发器执行不稳定(例如初始化/种子数据挂起或递归写入路径),则将约束维护逻辑迁移到显式的mutation辅助函数中。
  6. 鉴权/速率限制检查需放在服务端。
  7. 跨过程调用:在queries/mutations中使用
    create<Module>Handler(ctx)
    (零开销),除非需要验证;在actions/HTTP路由中使用
    create<Module>Caller(ctx)
    。在action上下文里,使用
    caller.actions.*
    调用action过程,使用
    caller.schedule.*
    进行任务调度。从
    ./generated/<module>.runtime
    导入。绝不要直接调用
    ctx.runQuery
    /
    ctx.runMutation
    /
    ctx.runAction
    来执行模块过程。

Shortcut Mode (tRPC + Drizzle Mental Model)

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

Default assumption:
  • cRPC behavior is tRPC-like (builder chain + middleware + TanStack options).
  • ORM behavior is Drizzle-like (schema, relations,
    findMany/findFirst
    ,
    insert/update/delete
    ). Only remember these non-parity deltas:
  1. Procedure input root must be
    z.object(...)
    (no primitive root args).
  2. No
    z.void()
    outputs; omit
    .output(...)
    for no-value mutations.
  3. Stacked
    .input(...)
    calls merge input shapes.
  4. .paginated({ limit, item })
    must be before
    .query()
    and auto-adds
    input.cursor
    +
    input.limit
    , output
    { page, continueCursor, isDone }
    .
  5. Metadata is codegen’d onto
    @convex/api
    leaves (
    api.namespace.fn.meta
    ) so never put secrets in
    .meta(...)
    ; chaining
    .meta(...)
    is shallow merge and supports
    defaultMeta
    .
  6. Auth metadata drives client behavior:
    auth: "optional"
    waits for auth load then runs,
    auth: "required"
    waits then skips when logged out.
  7. ctx.orm
    enforces constraints + RLS;
    ctx.db
    bypasses them.
  8. Non-paginated
    findMany()
    must be explicitly sized (
    limit
    , cursor mode, schema
    defaultLimit
    , or explicit
    allowFullScan
    ).
  9. Predicate
    where
    requires explicit
    .withIndex(...)
    ; no implicit full scan fallback.
  10. Cursor pagination uses the first
    orderBy
    field; index that field for stable paging.
  11. maxScan
    applies to cursor mode only;
    allowFullScan
    is for non-cursor full-scan opt-in.
  12. String operators /
    columns
    projection / many-relation subfilters can run post-fetch; bound result size early.
  13. Search mode is relevance-ordered and does not support
    orderBy
    ; vector mode has stricter limits (no cursor/offset/top-level where/order).
  14. Update/delete without
    where
    throws unless
    allowFullScan()
    .
  15. count()
    ,
    aggregate()
    , and
    groupBy()
    require a matching
    aggregateIndex
    . Use
    groupBy({ by, _count, _sum })
    instead of multiple
    .count()
    calls or
    findMany
    + manual JS grouping. Every
    by
    field must be finite-constrained (
    eq
    /
    in
    /
    isNull
    ) in
    where
    . See
    references/features/aggregates.md
    .
  16. cRPC React queries are real-time by default (
    subscribe: true
    ); never use
    queryClient.invalidateQueries
    for these subscribed paths.
  17. In RSC,
    prefetch
    hydrates client,
    caller
    is server-only and not hydrated,
    preloadQuery
    hydrates but can cause stale split ownership if also rendered client-side.
  18. Better Auth Next.js shortcut is
    convexBetterAuth(...)
    ; generic server-only shortcut is
    createCallerFactory(...)
    .
  19. On the kitcn auth client path, use
    createAuthMutations(authClient)
    wrappers so logout unsubscribes auth queries before sign out. Raw Convex preset keeps a smaller plain
    authClient
    .
  20. NEVER use
    ctx.runQuery
    /
    ctx.runMutation
    /
    ctx.runAction
    directly for module-to-module calls. Use
    create<Module>Handler(ctx)
    or
    create<Module>Caller(ctx)
    from
    convex/functions/generated/<module>.runtime
    instead.
  21. create<Module>Handler(ctx)
    — default choice for queries/mutations. Bypasses input validation, middleware, output validation → zero overhead. Query/mutation ctx only. Import from
    ./generated/<module>.runtime
    .
  22. create<Module>Caller(ctx)
    — use in actions and HTTP routes (where handler is unavailable). Goes through validation + middleware. Root caller exposes query+mutation procedures. In
    ActionCtx
    , action procedures are under
    caller.actions.*
    ; scheduling is under
    caller.schedule.now|after|at
    with
    caller.schedule.cancel(id)
    . Use
    requireActionCtx(ctx)
    only when the callback truly runs in
    ActionCtx
    and you need
    caller.actions.*
    . If the callback can run from
    MutationCtx | ActionCtx
    or generic scheduler-capable context, keep the seam honest: use
    requireSchedulerCtx(ctx)
    and
    caller.schedule.*
    instead of pretending the path is action-only. Import from
    ./generated/<module>.runtime
    . Each caller/handler eagerly loads every procedure in its module (no lazy loading) — split large modules to keep bundles lean.
  23. API types (
    Api
    ,
    ApiInputs
    ,
    ApiOutputs
    ,
    Select
    ,
    Insert
    ,
    TableName
    ) import from
    @convex/api
    — no manual
    inferApiInputs<typeof api>
    .
  24. HTTP router must export as
    httpRouter
    (not
    appRouter
    ) for codegen.
  25. Server wiring imports come from
    convex/functions/generated/
    directory:
    getAuth
    ,
    defineAuth
    from
    generated/auth
    ;
    initCRPC
    ,
    QueryCtx
    ,
    MutationCtx
    ,
    OrmCtx
    from
    generated/server
    ;
    create<Module>Caller
    ,
    create<Module>Handler
    from
    generated/<module>.runtime
    . No manual
    convex/lib/orm.ts
    .
  26. defineAuth(() => ({ ...options, triggers }))
    replaces split
    getAuthOptions
    +
    authTriggers
    . Trigger callbacks are doc-first:
    beforeCreate(data)
    ,
    onCreate(doc)
    ,
    onUpdate(newDoc, oldDoc)
    — no
    ctx
    first param.
  27. Internal auth functions at
    internal.generated.*
    (not
    internal.auth.*
    ).
  28. Async mutation batching is the default (codegen wires it). Customize per call:
    execute({ batchSize, delayMs })
    . Opt into sync:
    execute({ mode: 'sync' })
    or
    defineSchema(..., { defaults: { mutationExecutionMode: 'sync' } })
    . Relevant defaults:
    mutationBatchSize
    ,
    mutationLeafBatchSize
    ,
    mutationMaxRows
    ,
    mutationScheduleCallCap
    .
  29. Polymorphic unions are schema-first: use
    actionType: discriminator({ variants, as? })
    in
    convexTable(...)
    . Query config does not include a
    polymorphic
    option. Writes stay flat; reads synthesize nested
    details
    (or custom alias). Use
    withVariants: true
    to auto-load all
    one()
    relations on discriminator tables.
  30. Do not add manual ORM mutation batching loops in app/plugin code by default. Convex runtime batching already handles mutation execution. Prefer set-based deletes/updates over per-row loops. Only add explicit chunking when batching external side effects (for example Resend API calls) or bounded cleanup sweeps.
默认假设:
  • cRPC行为类似tRPC(构建器链 + 中间件 + TanStack配置)。
  • ORM行为类似Drizzle(schema、关联关系、
    findMany/findFirst
    insert/update/delete
    )。 只需记住以下差异点:
  1. 过程的根输入必须是
    z.object(...)
    (不支持原始类型根参数)。
  2. 不支持
    z.void()
    输出;无返回值的mutations需省略
    .output(...)
  3. 多次调用
    .input(...)
    会合并输入结构。
  4. .paginated({ limit, item })
    必须放在
    .query()
    之前,会自动添加
    input.cursor
    +
    input.limit
    ,输出
    { page, continueCursor, isDone }
  5. 元数据会自动生成到
    @convex/api
    的叶子节点(
    api.namespace.fn.meta
    ),因此绝不要在
    .meta(...)
    中存放敏感信息;链式调用
    .meta(...)
    会进行浅合并,并且支持
    defaultMeta
  6. 鉴权元数据控制客户端行为:
    auth: "optional"
    会等待鉴权加载完成后执行,
    auth: "required"
    会等待鉴权完成,若未登录则跳过执行。
  7. ctx.orm
    会强制约束+RLS(行级安全);
    ctx.db
    会绕过这些限制。
  8. 非分页的
    findMany()
    必须显式限制大小(
    limit
    、游标模式、schema的
    defaultLimit
    ,或显式声明
    allowFullScan
    )。
  9. 断言式
    where
    必须显式调用
    .withIndex(...)
    ;不支持隐式全表扫描回退。
  10. 游标分页使用第一个
    orderBy
    字段;需为该字段创建索引以保证分页稳定性。
  11. maxScan
    仅适用于游标模式;
    allowFullScan
    用于非游标模式下的全表扫描授权。
  12. 字符串操作符 /
    columns
    投影 / 多关联子过滤可在获取数据后执行;需尽早限制结果集大小。
  13. 搜索模式按相关性排序,不支持
    orderBy
    ;向量模式有更严格的限制(不支持游标/偏移/顶级where/排序)。
  14. 不带
    where
    的Update/Delete操作会抛出错误,除非声明
    allowFullScan()
  15. count()
    aggregate()
    groupBy()
    需要匹配的
    aggregateIndex
    。使用
    groupBy({ by, _count, _sum })
    替代多次
    .count()
    调用或
    findMany
    + 手动JS分组。
    where
    中的每个
    by
    字段必须是有限约束的(
    eq
    /
    in
    /
    isNull
    )。详见
    references/features/aggregates.md
  16. cRPC React查询默认是实时的(
    subscribe: true
    );对于这些已订阅的路径,绝不要使用
    queryClient.invalidateQueries
  17. 在RSC(React Server Components)中,
    prefetch
    会为客户端注入数据,
    caller
    仅服务端可用且不会注入数据,
    preloadQuery
    会注入数据,但如果同时在客户端渲染,可能会导致数据所有权不一致的过期问题。
  18. Better Auth Next.js快捷方式是
    convexBetterAuth(...)
    ;通用服务端快捷方式是
    createCallerFactory(...)
  19. 在kitcn鉴权客户端路径中,使用
    createAuthMutations(authClient)
    包装器,以便登出时先取消订阅鉴权查询再执行退出操作。原生Convex预设使用更轻量的普通
    authClient
  20. 绝不要直接使用
    ctx.runQuery
    /
    ctx.runMutation
    /
    ctx.runAction
    进行模块间调用。请使用
    convex/functions/generated/<module>.runtime
    中的
    create<Module>Handler(ctx)
    create<Module>Caller(ctx)
    替代。
  21. create<Module>Handler(ctx)
    —— queries/mutations的默认选择。绕过输入验证、中间件、输出验证 → 零开销。仅适用于Query/Mutation上下文。从
    ./generated/<module>.runtime
    导入。
  22. create<Module>Caller(ctx)
    —— 在actions和HTTP路由中使用(此时handler不可用)。会经过验证+中间件。根caller暴露query+mutation过程。在
    ActionCtx
    中,action过程位于
    caller.actions.*
    ;调度功能位于
    caller.schedule.now|after|at
    ,可使用
    caller.schedule.cancel(id)
    取消任务。仅当回调确实在
    ActionCtx
    中运行且需要
    caller.actions.*
    时,才使用
    requireActionCtx(ctx)
    。如果回调可在
    MutationCtx | ActionCtx
    或通用支持调度的上下文中运行,请保持逻辑清晰:使用
    requireSchedulerCtx(ctx)
    caller.schedule.*
    ,而非强行假设路径仅为action专用。从
    ./generated/<module>.runtime
    导入。每个caller/handler会预加载模块中的所有过程(无懒加载)—— 拆分大型模块以减小包体积。
  23. API类型(
    Api
    ApiInputs
    ApiOutputs
    Select
    Insert
    TableName
    )从
    @convex/api
    导入 —— 无需手动使用
    inferApiInputs<typeof api>
  24. HTTP路由必须导出为
    httpRouter
    (而非
    appRouter
    )以支持代码生成。
  25. 服务端配置导入来自
    convex/functions/generated/
    目录:
    getAuth
    defineAuth
    来自
    generated/auth
    initCRPC
    QueryCtx
    MutationCtx
    OrmCtx
    来自
    generated/server
    create<Module>Caller
    create<Module>Handler
    来自
    generated/<module>.runtime
    。不要手动导入
    convex/lib/orm.ts
  26. defineAuth(() => ({ ...options, triggers }))
    替代拆分的
    getAuthOptions
    +
    authTriggers
    。触发器回调以文档优先:
    beforeCreate(data)
    onCreate(doc)
    onUpdate(newDoc, oldDoc)
    —— 第一个参数不是
    ctx
  27. 内部鉴权函数位于
    internal.generated.*
    (而非
    internal.auth.*
    )。
  28. 异步mutation批处理是默认行为(代码生成已配置)。可按调用自定义:
    execute({ batchSize, delayMs })
    。启用同步模式:
    execute({ mode: 'sync' })
    defineSchema(..., { defaults: { mutationExecutionMode: 'sync' } })
    。相关默认配置:
    mutationBatchSize
    mutationLeafBatchSize
    mutationMaxRows
    mutationScheduleCallCap
  29. 多态联合是schema优先的:在
    convexTable(...)
    中使用
    actionType: discriminator({ variants, as? })
    。查询配置不包含
    polymorphic
    选项。写入保持扁平结构;读取时会合成嵌套的
    details
    (或自定义别名)。使用
    withVariants: true
    可自动加载鉴别器表上的所有
    one()
    关联关系。
  30. 默认情况下,不要在应用/插件代码中添加手动ORM mutation批处理循环。Convex运行时已处理mutation执行。优先使用基于集合的删除/更新而非逐行循环。仅在批处理外部副作用(例如Resend API调用)或有限清理扫描时添加显式分块逻辑。

Directory Boundary (Important)

目录边界(重要)

Use
references/setup/
when the task needs:
  1. Project/file structure setup →
    setup/index.md
    +
    setup/server.md
  2. Auth bootstrap →
    setup/auth.md
  3. Client/provider wiring →
    setup/react.md
  4. Framework-specific setup →
    setup/next.md
    or
    setup/start.md
    For full template-level recreation: start with
    setup/index.md
    , then load relevant setup files, then load selected feature refs.
当任务需要以下操作时,请使用
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
    开始,然后加载相关搭建文件,再加载选定的功能参考文档。

First-Pass Feature Intake (Do This Before Edits)

功能初步梳理(编辑前必做)

Lock these decisions first:
  1. Auth level per endpoint:
    public
    /
    optionalAuth
    /
    auth
    /
    private
    .
  2. Data invariants: what must always be true after writes?
  3. Query shape: list, detail, relation-loaded, search, or stream composition.
  4. Pagination mode: offset, cursor, infinite.
  5. Side effects: trigger vs scheduled function vs inline mutation.
  6. UI consumption: client hook only, RSC prefetch, or server-only caller.
  7. Risk paths: unauthorized, forbidden, not found, conflicts, rate limit.
先确定以下决策:
  1. 每个端点的鉴权级别:
    public
    /
    optionalAuth
    /
    auth
    /
    private
  2. 数据约束:写入后必须始终满足哪些条件?
  3. 查询类型:列表、详情、关联加载、搜索或流组合。
  4. 分页模式:偏移量、游标、无限滚动。
  5. 副作用:触发器、调度函数还是内联mutation。
  6. UI使用方式:仅客户端钩子、RSC预取或仅服务端caller。
  7. 风险路径:未授权、禁止访问、资源不存在、冲突、速率限制。

Canonical File Targets

标准文件目标

Typical feature touches:
  • convex/functions/schema.ts
  • convex/functions/<feature>.ts
  • convex/lib/crpc.ts
    (only if middleware/procedure builder changes)
  • src/lib/convex/crpc.tsx
    (only if cRPC context/meta wiring changes)
  • src/**
    feature UI files
  • convex/functions/http.ts
    or
    convex/routers/**
    for HTTP endpoints
  • convex/functions/crons.ts
    or scheduled handlers if needed
典型功能开发会涉及以下文件:
  • convex/functions/schema.ts
  • convex/functions/<feature>.ts
  • convex/lib/crpc.ts
    (仅当中间件/过程构建器变更时)
  • src/lib/convex/crpc.tsx
    (仅当cRPC上下文/元数据配置变更时)
  • src/**
    功能UI文件
  • convex/functions/http.ts
    convex/routers/**
    (用于HTTP端点)
  • convex/functions/crons.ts
    或调度处理器(如有需要)

E2E Build Order (Default)

端到端构建顺序(默认)

  1. Schema + indexes + relations.
  2. Trigger hooks for cross-row invariants (or explicit mutation-side sync if trigger path is unstable).
  3. Procedures with strict input/output + auth + rate limits.
  4. React hooks (query/mutation/infinite) using cRPC options.
  5. Optional: HTTP route(s), scheduling hooks.
  6. Tests for auth/error/trigger behavior.
  1. Schema + 索引 + 关联关系。
  2. 用于跨行约束的触发器钩子(如果触发器路径不稳定,则使用显式mutation端同步逻辑)。
  3. 带有严格输入/输出 + 鉴权 + 速率限制的过程。
  4. 使用cRPC配置的React钩子(query/mutation/infinite)。
  5. 可选:HTTP路由、调度钩子。
  6. 鉴权/错误/触发器行为测试。

Core Patterns

核心模式

1) Schema + Relations + Trigger

1) Schema + 关联关系 + 触发器

ts
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 rules that matter:
  1. Index fields that power filters/order/search.
  2. many()
    relation paths need child FK indexes.
  3. Trigger logic must be bounded and non-recursive.
  4. Use table defaults for consistent write behavior.
  5. Keep full ORM/query edge cases in
    references/features/orm.md
    .
ts
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) Procedure Builders + Middleware

2) 过程构建器 + 中间件

ts
import { getHeaders } from "kitcn/auth";
import { CRPCError } from "kitcn/server";
import { getAuth } from "../functions/generated/auth";
import { initCRPC } 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;
}
export const publicQuery = c.query.meta({ auth: "optional" });
export const authQuery = c.query
  .meta({ auth: "required" })
  .use(async ({ ctx, next }) => {
    const auth = getAuth(ctx);
    const session = await auth.api.getSession({
      headers: await getHeaders(ctx),
    });
    const user = requireAuth(session?.user ?? null);
    return next({ ctx: { ...ctx, user, userId: user.id } });
  });
export const authMutation = c.mutation
  .meta({ auth: "optional" })
  .use(async ({ ctx, next }) => {
    const auth = getAuth(ctx);
    const session = await auth.api.getSession({
      headers: await getHeaders(ctx),
    });
    return next({
      ctx: {
        ...ctx,
        user: session?.user ?? null,
        userId: session?.user?.id ?? null,
      },
    });
  });
Builder rules that matter:
  1. Build
    public
    ,
    optional
    ,
    auth
    , and
    private
    procedure families once in
    convex/lib/crpc.ts
    .
  2. .meta(...)
    is client-visible via generated API metadata. Never put secrets there.
  3. Middleware receives server-only
    procedure
    info. When procedures are built from your app
    generated/server
    helper, standard
    export const
    queries, mutations, and actions infer
    module:function
    automatically from file path + export name. Use
    .name("module:function")
    only to override or cover unusual export shapes.
  4. Resolve session/user once in middleware. Do not re-fetch auth state in every procedure.
  5. Shared
    c.middleware()
    chains preserve mutation writer types on mutation procedures. If the middleware itself performs writes, type it as mutation-only with
    c.middleware<MutationCtx>(...)
    .
  6. Keep deeper auth/runtime edge cases in
    references/setup/server.md
    and
    references/features/auth*.md
    .
ts
import { getHeaders } from "kitcn/auth";
import { CRPCError } from "kitcn/server";
import { getAuth } from "../functions/generated/auth";
import { initCRPC } 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;
}
export const publicQuery = c.query.meta({ auth: "optional" });
export const authQuery = c.query
  .meta({ auth: "required" })
  .use(async ({ ctx, next }) => {
    const auth = getAuth(ctx);
    const session = await auth.api.getSession({
      headers: await getHeaders(ctx),
    });
    const user = requireAuth(session?.user ?? null);
    return next({ ctx: { ...ctx, user, userId: user.id } });
  });
export const authMutation = c.mutation
  .meta({ auth: "optional" })
  .use(async ({ ctx, next }) => {
    const auth = getAuth(ctx);
    const session = await auth.api.getSession({
      headers: await getHeaders(ctx),
    });
    return next({
      ctx: {
        ...ctx,
        user: session?.user ?? null,
        userId: session?.user?.id ?? null,
      },
    });
  });
构建器关键规则:
  1. convex/lib/crpc.ts
    中一次性构建
    public
    optional
    auth
    private
    过程族。
  2. .meta(...)
    可通过生成的API元数据被客户端可见。绝不要在其中存放敏感信息。
  3. 中间件接收仅服务端可见的
    procedure
    信息。当过程从应用的
    generated/server
    助手构建时,标准的
    export const
    queries、mutations和actions会自动从文件路径+导出名称推断
    module:function
    。仅在需要覆盖或处理特殊导出结构时使用
    .name("module:function")
  4. 在中间件中一次性解析会话/用户信息。不要在每个过程中重复获取鉴权状态。
  5. 共享的
    c.middleware()
    链会保留mutation过程的写入器类型。如果中间件本身执行写入操作,请将其类型标记为仅mutation可用:
    c.middleware<MutationCtx>(...)
  6. 更深入的鉴权/运行时边缘案例请参考
    references/setup/server.md
    references/features/auth*.md

3) Query + Mutation Procedure Template

3) 查询 + 变更过程模板

ts
import { 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,
      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;
  });
Procedure rules that matter:
  1. Root input must be
    z.object(...)
    .
  2. Use strict
    .input(...)
    ; add
    .output(...)
    only when needed.
  3. Omit
    .output(...)
    for no-value mutations.
  4. Use the default mutation rate limit; add
    .meta({ ratelimit: ... })
    only for named bucket overrides.
  5. Throw
    CRPCError
    for expected outcomes.
  6. Bound every list with
    limit
    , cursor, or
    .paginated(...)
    .
  7. Move advanced query-builder shapes to
    references/features/orm.md
    .
ts
import { 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,
      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. 无返回值的mutations需省略
    .output(...)
  4. 使用默认的mutation速率限制;仅在需要覆盖命名桶时添加
    .meta({ ratelimit: ... })
  5. 预期结果抛出
    CRPCError
  6. 每个列表查询必须通过
    limit
    、游标或
    .paginated(...)
    限制范围。
  7. 高级查询构建器结构请参考
    references/features/orm.md

3b) Inter-Procedure Composition

3b) 跨过程组合

Use:
  1. create<Module>Handler(ctx)
    in queries/mutations.
  2. create<Module>Caller(ctx)
    in actions/HTTP routes.
  3. caller.actions.*
    for action procedures.
  4. caller.schedule.*
    for scheduled procedures.
  5. Never
    ctx.runQuery
    /
    ctx.runMutation
    /
    ctx.runAction
    for module procedures.
使用:
  1. 在queries/mutations中使用
    create<Module>Handler(ctx)
  2. 在actions/HTTP路由中使用
    create<Module>Caller(ctx)
  3. 使用
    caller.actions.*
    调用action过程。
  4. 使用
    caller.schedule.*
    调用调度过程。
  5. 绝不要使用
    ctx.runQuery
    /
    ctx.runMutation
    /
    ctx.runAction
    调用模块过程。

4) Query Modes (Use The Right One)

4) 查询模式(选择合适的模式)

  1. Default to object
    where
    .
  2. Use callback
    where
    only when composition reads better than object form.
  3. Predicate/filter callbacks require
    .withIndex(...)
    first plus explicit
    limit
    /
    maxScan
    .
  4. Full-text search uses
    search: { index, query, filters }
    and does not support
    orderBy
    .
  5. Cursor paging is only stable when the
    orderBy
    field is indexed.
  6. Advanced modes (
    pageByKey
    , vector search, pipelines, aggregate indexes) live in
    references/features/orm.md
    .
  1. 默认使用对象形式的
    where
  2. 仅当组合逻辑比对象形式更易读时,才使用回调形式的
    where
  3. 断言/过滤回调必须先调用
    .withIndex(...)
    ,并显式指定
    limit
    /
    maxScan
  4. 全文搜索使用
    search: { index, query, filters }
    ,不支持
    orderBy
  5. 游标分页仅在
    orderBy
    字段已索引时才稳定。
  6. 高级模式(
    pageByKey
    、向量搜索、管道、聚合索引)请参考
    references/features/orm.md

5) Mutation Patterns (Most Used)

5) 变更模式(最常用)

  1. Use
    .returning(...)
    on inserts when caller needs created ids.
  2. Every update/delete path gets an explicit
    where(...)
    .
  3. Clear optional columns with
    unsetToken
    .
  4. Async mutation execution is the default; use
    .execute({ mode: "sync" })
    only when atomic all-at-once behavior is required.
  5. Prefer set-based deletes/updates. Add chunking only for external side effects or bounded cleanups.
  6. Upsert, conflict handling, mutation batching, and schema extension edge cases live in
    references/features/orm.md
    .
  1. 当调用者需要创建的ID时,在插入操作上使用
    .returning(...)
  2. 每个更新/删除路径都必须有显式的
    where(...)
  3. 使用
    unsetToken
    清空可选列。
  4. 异步mutation执行是默认行为;仅当需要原子性一次性执行时,才使用
    .execute({ mode: "sync" })
  5. 优先使用基于集合的删除/更新。仅在处理外部副作用或有限清理时添加分块逻辑。
  6. Upsert、冲突处理、mutation批处理和schema扩展边缘案例请参考
    references/features/orm.md

6) Error Model

6) 错误模型

Use this map consistently:
  1. BAD_REQUEST
    : invalid input or business precondition.
  2. UNAUTHORIZED
    : no session.
  3. FORBIDDEN
    : session exists, permission missing.
  4. NOT_FOUND
    : missing or inaccessible resource.
  5. CONFLICT
    : duplicate or conflicting write.
  6. TOO_MANY_REQUESTS
    : rate limit.
  7. INTERNAL_SERVER_ERROR
    : unexpected failures only.
  8. Add small custom
    data
    payloads on
    CRPCError
    when the client needs domain metadata like conflicting ids. Read them on the client from
    error.data
    .
Required tests:
  1. unauthenticated rejection
  2. permission rejection when relevant
  3. missing resource path
  4. conflict path when relevant
  5. rate-limited write path when relevant
请统一使用以下映射:
  1. BAD_REQUEST
    :无效输入或业务前置条件不满足。
  2. UNAUTHORIZED
    :无会话。
  3. FORBIDDEN
    :存在会话,但缺少权限。
  4. NOT_FOUND
    :资源不存在或无法访问。
  5. CONFLICT
    :重复或冲突写入。
  6. TOO_MANY_REQUESTS
    :触发速率限制。
  7. INTERNAL_SERVER_ERROR
    :仅用于意外失败。
  8. 当客户端需要领域元数据(如冲突ID)时,可在
    CRPCError
    中添加小型自定义
    data
    负载。客户端可从
    error.data
    读取这些数据。
必测场景:
  1. 未认证请求被拒绝
  2. 相关权限被拒绝
  3. 资源不存在路径
  4. 冲突路径(如有相关场景)
  5. 速率限制写入路径(如有相关场景)

7) React Query Integration

7) React Query集成

Preconditions (must be true before writing/using
useCRPC()
code paths):
  1. Generated imports exist (
    @convex/api
    ) from setup bootstrap.
  2. Provider chain is mounted (
    CRPCProvider
    inside QueryClient + Convex provider flow).
  3. If bootstrap/provider prerequisites are missing, stop feature work and finish
    references/setup/
    first.
  4. Backend state is project-local in
    .convex/
    , not
    ~/.convex
    .
useCRPC()
pattern:
const crpc = useCRPC(); const projects = useQuery(crpc.project.listProjects.queryOptions({ cursor: null, limit: 20 })); const createProject = useMutation(crpc.project.createProject.mutationOptions());
Key client defaults/deltas:
  1. Queries are real-time by default (
    subscribe: true
    ).
  2. Never use
    queryClient.invalidateQueries
    for subscribed cRPC query paths.
  3. Use
    { subscribe: false }
    only for one-time fetches; refresh those with explicit
    refetch
    /
    fetchQuery
    .
  4. Use
    skipUnauth: true
    to avoid unauthorized fetch churn.
  5. For pagination, use
    useInfiniteQuery
    from
    kitcn/react
    .
  6. Prefer typed
    queryKey(...)
    helpers for cache read/write/fetch ops instead of manual keys.
  7. For kitcn auth flows, prefer
    createAuthMutations(...)
    wrappers (not raw auth client calls) to avoid logout race errors. Raw Convex preset keeps the plain auth client path.
  8. For mutation toasts, prefer
    error.data?.message
    over
    error.message
    ;
    data.message
    is the clean
    CRPCError
    payload.
  9. Prefer one global
    QueryClient
    mutation
    onError
    toast with
    mutation.meta.errorMessage
    /
    skipErrorToast
    rather than copy-pasting
    onError
    in every component.
  10. Full client/RSC depth lives in
    references/features/react.md
    .
前置条件(编写/使用
useCRPC()
代码路径前必须满足):
  1. 已存在生成的导入(
    @convex/api
    ),来自搭建初始化流程。
  2. 已挂载提供者链(
    CRPCProvider
    位于QueryClient + Convex提供者流程内部)。
  3. 如果缺少初始化/提供者前置条件,请停止功能开发,先完成
    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 Patterns (Next.js)

8) RSC模式(Next.js)

Choose one per use case:
  1. prefetch(...)
    (preferred): non-blocking, hydrated, client owns data.
  2. caller.*
    : blocking server-only logic (redirects/auth checks), not hydrated.
  3. preloadQuery(...)
    : blocking + hydrated when server needs data immediately.
Do not render
preloadQuery
result on server and again on client for the same data path.
  1. HydrateClient
    must wrap all client components that consume prefetched queries.
  2. Next.js-specific setup and deeper hydration tradeoffs live in
    references/setup/next.md
    and
    references/features/react.md
    .
根据使用场景选择一种:
  1. prefetch(...)
    (推荐):非阻塞、注入数据、客户端拥有数据所有权。
  2. caller.*
    :阻塞式仅服务端逻辑(重定向/鉴权检查),不注入数据。
  3. preloadQuery(...)
    :阻塞式+注入数据,适用于服务端需要立即获取数据的场景。
不要在服务端渲染
preloadQuery
结果,同时在客户端再次渲染同一数据路径。
  1. HydrateClient
    必须包裹所有使用预取查询的客户端组件。
  2. Next.js特定搭建和更深入的注入权衡请参考
    references/setup/next.md
    references/features/react.md

9) HTTP Route Pattern (When Feature Needs REST/Webhooks)

9) HTTP路由模式(当功能需要REST/Webhooks时)

ts
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-specific rules:
  1. Use
    z.coerce.*
    for search params.
  2. Keep auth and permission checks in middleware/procedure.
  3. Apply rate limits to public/heavy endpoints.
  4. Validate webhook signatures before any side effects.
  5. Use
    publicRoute
    /
    authRoute
    /
    optionalAuthRoute
    builders from
    convex/lib/crpc.ts
    .
  6. Compose endpoints with
    router(...)
    for feature-level HTTP grouping.
  7. Client calls must pass path/query args as
    { params, searchParams }
    ; query values are strings.
  8. Webhooks, streaming, and Hono-specific patterns live in
    references/features/http.md
    .
ts
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. 将鉴权和权限检查放在中间件/过程中。
  3. 对公共/高负载端点应用速率限制。
  4. 在执行任何副作用前验证Webhook签名。
  5. 使用
    convex/lib/crpc.ts
    中的
    publicRoute
    /
    authRoute
    /
    optionalAuthRoute
    构建器。
  6. 使用
    router(...)
    组合端点,实现功能级HTTP分组。
  7. 客户端调用必须将路径/查询参数作为
    { params, searchParams }
    传递;查询值为字符串类型。
  8. Webhooks、流和Hono特定模式请参考
    references/features/http.md

10) Scheduling Pattern (If Needed)

10) 调度模式(如有需要)

Example:
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 });
Scheduling rules:
  1. Auth context is not propagated; pass user/org IDs explicitly.
  2. Mutation scheduling is atomic with the mutation transaction.
  3. Store returned job IDs when cancellation is required.
  4. Scheduling inside actions is not atomic with action failure.
  5. Cron schedules run in UTC.
  6. Use
    ctx.scheduler.*
    directly only when you must schedule non-procedure
    internal.*
    functions.
  7. Cron expressions and operational details live in
    references/features/scheduling.md
    .
示例:
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. 在actions内部调度的任务与action失败不具有原子性。
  5. Cron调度在UTC时区运行。
  6. 仅当必须调度非过程的
    internal.*
    函数时,才直接使用
    ctx.scheduler.*
  7. Cron表达式和操作细节请参考
    references/features/scheduling.md

11) Testing Baseline (High Signal)

11) 测试基准(高价值)

Minimum feature test set:
  1. happy path query/mutation
  2. unauthenticated rejection (
    UNAUTHORIZED
    )
  3. permission/ownership rejection (
    FORBIDDEN
    where relevant)
  4. missing resource (
    NOT_FOUND
    )
  5. trigger side effect assertion
  6. scheduler assertion if feature schedules work
  7. not-found checks should use real IDs or non-ID lookup keys (slug/name/email), not synthetic IDs
  8. Full testing recipes live in
    references/features/testing.md
    .
  9. If Convex bootstrap blocks integration tests, extract pure guards/helpers and keep one smoke integration test once bootstrap works.
功能测试最小集合:
  1. 查询/mutation正常路径
  2. 未认证请求被拒绝(
    UNAUTHORIZED
  3. 权限/所有权被拒绝(相关场景下的
    FORBIDDEN
  4. 资源不存在(
    NOT_FOUND
  5. 触发器副作用断言
  6. 若功能包含调度任务,则需断言调度逻辑
  7. 资源不存在检查应使用真实ID或非ID查找键(slug/名称/邮箱),而非合成ID
  8. 完整测试方案请参考
    references/features/testing.md
  9. 如果Convex初始化阻碍集成测试,请提取纯守卫/辅助函数,待初始化完成后保留一个冒烟集成测试。

Performance + Safety Checklist

性能 + 安全检查清单

Before calling a feature done:
  1. Every list query is bounded (
    limit
    /cursor).
  2. Filters/order align with indexes.
  3. Expensive post-fetch logic uses pre-narrowed index path.
  4. Mutations use targeted
    where
    and avoid accidental full scans.
  5. Trigger logic is bounded, idempotent, and avoids ping-pong loops.
  6. Error codes are explicit and intentional.
  7. User-facing writes have rate-limit metadata.
  8. Tests cover auth + not-found + side effects.
  9. ctx.db
    is not used on paths that rely on ORM constraints/RLS.
  10. Paginated endpoints use
    .paginated(...)
    + ORM cursor flow (not ad-hoc wrappers).
  11. For any predicate/full-scan-like path,
    .withIndex(...)
    + bound (
    limit
    /
    maxScan
    ) is explicit.
  12. NEVER use
    @ts-nocheck
    , no global lint-rule downgrades, no unresolved lint warnings in touched files.
在标记功能完成前,请确认:
  1. 每个列表查询都已限制范围(
    limit
    /游标)。
  2. 过滤/排序与索引对齐。
  3. 昂贵的获取后逻辑使用预先缩小范围的索引路径。
  4. Mutations使用针对性的
    where
    ,避免意外全表扫描。
  5. 触发器逻辑有限制、幂等且避免循环调用。
  6. 错误码明确且符合预期。
  7. 用户可见的写入操作已配置速率限制元数据。
  8. 测试覆盖鉴权+资源不存在+副作用场景。
  9. 依赖ORM约束/RLS的路径未使用
    ctx.db
  10. 分页端点使用
    .paginated(...)
    + ORM游标流程(而非临时包装器)。
  11. 对于任何断言/类全表扫描路径,已显式调用
    .withIndex(...)
    + 限制(
    limit
    /
    maxScan
    )。
  12. 绝不要使用
    @ts-nocheck
    ,不要全局降级 lint 规则,不要保留修改文件中未解决的 lint 警告。

Common Mistakes (And Fixes)

常见错误(及修复方案)

MistakeCorrect pattern
Raw Convex handler for new feature procedurescRPC builders (
publicQuery
,
authMutation
, etc.)
Write-time side effects duplicated across mutationsSchema trigger, or one centralized mutation-side sync helper when trigger path is unsafe
Missing bounds on list/searchAdd
limit
+ cursor/pagination
orderBy
written as array objects
Use object form:
orderBy: { updatedAt: "desc" }
Using
ctx.db
for policy-sensitive reads
Use
ctx.orm
(RLS/constraints path)
Throwing generic
Error
for expected outcomes
Throw
CRPCError
with explicit code
Infinite list with TanStack native hook directlyUse
useInfiniteQuery
from
kitcn/react
Primitive root input (
z.string()
)
Use root
z.object(...)
input schema
Returning nothing with
z.void()
Omit explicit output
Manual pagination wrappers for infinite endpointsUse
.paginated({ limit, item })
Synthetic Convex IDs in tests (
"missing-id"
)
Use inserted IDs or semantic lookup keys
Aggregates disabled but helper/config still presentRemove aggregate helper +
defineTriggers
handlers + app config together
Putting secrets in
.meta(...)
Keep metadata non-sensitive (client-visible)
Using
ctx.runQuery
/
ctx.runMutation
/
ctx.runAction
directly
Use
create<Module>Handler(ctx)
in queries/mutations,
create<Module>Caller(ctx)
in actions/HTTP with
caller.actions.*
/
caller.schedule.*
(from
generated/<module>.runtime
)
Using
createCaller
in query/mutation context
Use
create<Module>Handler(ctx)
— zero overhead, bypasses redundant validation
Adding
// @ts-nocheck
to unblock compile
NEVER do this; fix the underlying types using canonical patterns in
references/setup/
Relaxing lint rules to pass checksKeep baseline lint config; fix code-level warnings/errors instead
错误操作正确模式
新功能过程使用原生Convex处理器使用cRPC构建器(
publicQuery
authMutation
等)
写入时副作用在多个mutations中重复实现使用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()
返回空值
省略显式输出
为无限端点手动编写分页包装器使用
.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配置;修复代码级警告/错误

Reference Escalation Map (Load Only If Needed)

参考文档升级映射(仅在需要时加载)

Setup (once per project):
  • references/setup/index.md
    : bootstrap, env, decision intake, gates, checklist, troubleshooting
  • references/setup/server.md
    : core backend (schema, ORM, cRPC) + optional module gates
  • references/setup/auth.md
    : auth core bootstrap + plugin setup
  • references/setup/react.md
    : client core (QueryClient, provider, cRPC context)
  • references/setup/next.md
    : Next.js App Router setup
  • references/setup/start.md
    : TanStack Start setup
  • references/setup/doc-guidelines.md
    : skill/docs sync contract
Features (per session, self-contained):
  • references/features/orm.md
    : full ORM API, constraints, RLS, advanced mutations, filtering/search/composition/pagination
  • references/features/react.md
    : full client, RSC, hydration, error handling matrix
  • references/features/http.md
    : typed REST routes, webhooks, streaming
  • references/features/scheduling.md
    : cron + delayed job patterns
  • references/features/testing.md
    : deeper testing scenarios
  • references/features/aggregates.md
    : aggregate component patterns
  • references/features/migrations.md
    : built-in online data migrations (defineMigration, CLI, deploy, drift). Load when: task involves data backfills, optional→required field hardening, field renames/removals, type narrowing, or
    kitcn migrate
    CLI commands. Skip for backward-compatible changes (new optional fields, new tables, code-level defaults).
  • references/features/create-plugins.md
    : canonical plugin authoring patterns (split package entries, token config, scaffold/lockfile/CLI manifest rules). Load when: creating or refactoring plugins.
  • references/features/auth.md
    : full Better Auth core flow
  • references/features/auth-admin.md
    : admin plugin details
  • references/features/auth-organizations.md
    : org/multi-tenant plugin details
搭建(每个项目一次):
  • references/setup/index.md
    :初始化、环境、决策梳理、准入条件、检查清单、故障排查
  • references/setup/server.md
    :核心后端(schema、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、高级mutations、过滤/搜索/组合/分页
  • references/features/react.md
    :完整客户端、RSC、数据注入、错误处理矩阵
  • references/features/http.md
    :类型化REST路由、Webhooks、流
  • 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/auth.md
    :完整Better Auth核心流程
  • references/features/auth-admin.md
    :admin插件细节
  • references/features/auth-organizations.md
    :组织/多租户插件细节