Convex Components:概念、隔离模型与使用方式

8月 12, 2026
Convex, TypeScript, Database, ByAI

AI 参与说明(Agent:/root、/root/components_docs、/root/components_source、/root/components_landscape):本文由 Agent 根据 Convex 官方文档、Components Directory、官方 npm 包说明与公开源码辅助整理,重点核对 Component 的隔离边界、调用语义、安装步骤和示例 API。资料核验于 2026-08-12;Components、CLI 与各个包的 API 都可能演进,实施前请以文末一手资料、项目 lockfile 和目标版本为准。

适用范围:本文面向已有 Convex TypeScript 项目、想复用现成后端能力或准备编写自己的 Component 的开发者。文中示例需要一个已配置 authentication 的 development deployment;示例只说明 application-layer rate limiting,不替代 network-layer DDoS 防护、身份认证或第三方服务的安全审查。

先说结论#

Convex Components 不是 UI 组件,也不只是“从 npm 安装一个 helper”。它是一个可安装到现有 Convex deployment 的、带私有状态的 backend module:可以包含 functions、schema、database tables、File Storage、scheduled functions 和独立的函数执行环境。应用与 Component 通过显式 API 交互,而不是互相直接读写表。Components Overview Understanding Components

这带来三项实际价值:

  • 使用方可以把 rate limiting、durable workflow、aggregate、AI agent 等有状态能力作为一个明确的后端边界接入,而非把私有实现表混进应用 schema。
  • Component 作者可以封装内部表、索引、调度和数据不变量,只公开稳定的 function API。
  • 调用仍发生在同一 Convex backend 的 transaction 模型内;它不是为了复用功能而额外引入一个需要自己运维的 microservice。

不过,官方 API reference 目前仍将 Components 相关的 AppDefinitionComponentDefinitionFunctionHandle 标记为 beta/unstable。因此,生产项目应锁定 convex 与 Component npm 包版本,在 dev 或 preview deployment 运行 codegen、typecheck 和关键 integration test 后再升级。convex/server API reference

1. Components 解决的是什么问题#

可以把一个 Component 理解为“可组合的小型 Convex backend”,而不是普通的 TypeScript library。

选择状态与执行位置适用情形不适合的情形
普通 npm library在应用自身的函数环境中运行,直接使用应用传入的 ctx纯算法、格式化、无私有持久状态的共享代码需要隐藏自己的 tables、scheduler 或复杂状态机
应用内模块使用主应用 schema 与主应用 functions业务数据只服务当前应用,且与主领域模型紧密耦合希望跨多个项目复用并独立维护数据不变量
Convex Component自带 functions、schema、data 与 sandbox;通过 ComponentApi 调用rate limiting、workflow、aggregate、协作、集成等可复用且有持久状态的能力只需一两个纯函数,或必须由外部团队独立运行、独立扩缩容的服务
外部服务独立网络/运维边界,通常经 action 调用第三方支付、邮件、LLM、公司已有的独立系统只是为了在 Convex 内复用一个有状态模块

@convex-dev/rate-limiter 是一个直观例子:它可以把分片、窗口算法和私有表藏在 Component 内,只让应用声明限流规则并调用 limit()@convex-dev/workflow 则把持久化步骤和重放日志封装起来。两者都不是 Convex 内核本身,而是建立在 Convex transaction、scheduler 和 functions 之上的应用层能力。

2. 隔离模型:有边界,但不是另一个 SaaS#

Component instance 形成一棵树:主应用在根,主应用或其他 Component 可以安装 child Component。同一个 Component 以不同 name 多次 use() 时,会得到各自独立的 tables 和 functions。Authoring Components Using Components

flowchart TD
    browser["Browser / React client"] --> publicFn["App public query / mutation / action<br/>authentication、authorization 与业务校验"]
    publicFn --> componentApi["components.rateLimiter<br/>ComponentApi"]
    componentApi --> sandbox["Rate Limiter Component sandbox<br/>functions · schema · private tables · scheduler"]
    sandbox --> result["validated result / error"]
    result --> publicFn
    publicFn --> browser
    callback["Explicit FunctionHandle<br/>仅在需要回调 app 时传入"] --> sandbox

这个模型的关键不变量如下:

  • Component 不能任意读取主应用的 tables、File Storage、environment variables 或 scheduled functions;主应用也不能直接修改 Component 数据。
  • Component 的 global variables 和对运行环境的 patch 不会与主应用或其他 Component 共享。
  • 跨边界的数据只通过 function arguments、return values 或显式 FunctionHandle 流动;arguments 与 return values 仍受 runtime validation 约束。
  • 这种隔离只说明 Convex 的 data/function boundary,不等于一个第三方 npm 包已经通过安全、许可或供应链审计。安装前仍应阅读该包的 README、LICENSE、release、依赖和可能的 external API / webhook 行为。

官方将 Components Directory 定义为目录,而不是单一的官方支持级别。目录中既有社区项目,也有 Convex 团队项目;若只看团队维护的条目,应使用目录提供的 get-convex / @convex-dev 官方清单。每个 Component 的许可证、维护状态和外部依赖都应单独核对。Components Directory

3. 使用一个 Component 的标准流程#

以下四步适用于大多数 npm Component。这里选用官方 Rate Limiter,因为它同时展示了安装、codegen、宿主应用 wrapper 与 transaction 语义。

3.1 安装并注册#

前提是已有一个 Convex 项目,并且 convex/ 已是该项目的 backend 目录。若已经有 convex/convex.config.ts,应把 app.use(...) 合并进去,不要覆盖已有 Component 配置。

npm install @convex-dev/rate-limiter
// convex/convex.config.ts
import { defineApp } from "convex/server";
import rateLimiter from "@convex-dev/rate-limiter/convex.config.js";

const app = defineApp();

// 默认 instance name 是 "rateLimiter"。
app.use(rateLimiter);

export default app;

随后运行开发命令:

npx convex dev --once

npx convex dev 会针对已配置的 dev deployment 更新 generated code 并推送当前 backend 代码;成功后,应用的 convex/_generated/api 才会拥有 components.rateLimiter。如果 Component 从未注册或 codegen 尚未成功,就不应手写或强制转换这个类型。npx convex dev Using Components

3.2 在宿主应用创建一个 TypeScript facade#

RateLimiter class 是这个 Component 提供的高层 facade。它内部会用 components.rateLimiter 调用 Component API;应用侧只保留业务可读的限流名称和配置。

// convex/rateLimiter.ts
import { MINUTE, RateLimiter } from "@convex-dev/rate-limiter";
import { components } from "./_generated/api.js";

export const rateLimiter = new RateLimiter(components.rateLimiter, {
  sendMessage: {
    kind: "token bucket",
    rate: 10,
    period: MINUTE,
    capacity: 3,
  },
});

上例的 token bucket 以每分钟 10 个 token 的速率恢复容量,capacity: 3 限制空闲后一次可积累的突发额度。它只是示例参数;应按用户、团队、API 配额和可接受的峰值来设计。对于高并发的热点规则,Component 还提供 shards,但分片是降低 transaction contention 的工程手段,不是无限扩容承诺。Rate Limiter README

3.3 由应用的 public mutation 承担 authentication 和 authorization#

Component 的 functions 不能被 browser/React client 直接当作主应用 api 调用。主应用看到的 components.rateLimiterComponentApi:其中 Component 的 public functions 在跨边界后成为 internal references,只能在 Convex function 中通过 ctx.runQueryctx.runMutationctx.runAction 调用。应在主应用 wrapper 中完成 authentication、authorization、输入校验和业务写入。Component API

下面是可放进一个已启用 authentication 的 TypeScript 项目的最小骨架。tokenIdentifier 从服务端 identity 得到,而不是让 client 自报限流 key。

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

export default defineSchema({
  messages: defineTable({
    author: v.string(),
    body: v.string(),
  }),
});
// convex/messages.ts
import { v } from "convex/values";
import { mutation } from "./_generated/server.js";
import { rateLimiter } from "./rateLimiter.js";

export const send = mutation({
  args: { body: v.string() },
  returns: v.id("messages"),
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity();
    if (identity === null) {
      throw new Error("Unauthenticated");
    }

    await rateLimiter.limit(ctx, "sendMessage", {
      key: identity.tokenIdentifier,
      throws: true,
    });

    return await ctx.db.insert("messages", {
      author: identity.tokenIdentifier,
      body: args.body,
    });
  },
});

预期行为是:已认证用户调用 api.messages.send 时,服务端先检查并消费 sendMessage 的额度;额度不足时 throws: true 抛出 ConvexError,未被捕获的错误会使整个 top-level mutation 失败,消息不会插入。不要用 client 传入的 userId、IP 或任意字符串作为授权依据;若需要 anonymous 限流,应在服务端设计可信的 session / device / challenge 机制。

本文没有连接或创建真实 Convex deployment 来执行该示例;代码结构已按当前官方文档和 Rate Limiter 源码核对。接入自己的项目时,运行 npx convex dev --once,预期会完成 codegen、typecheck 并把 Component 安装到已配置的 dev deployment;随后用你的已认证 client integration test 验证“超限时不写入消息”这一业务不变量。

这个例子同时说明了正确分层:ctx.auth 在主应用 function 可用,而 Rate Limiter Component 只收到被显式传入的 key。Component 无法顺藤摸瓜读取主应用用户表或身份配置。

4. 跨 Component 调用仍保留 Convex function 规则#

components.foo 不是浏览器可公开调用的 api.foo。调用者的 function 类型决定能调用 Component 的哪类 function:

调用者可调用的 Component function结果
queryquery仍是 reactive query;不能写入 Component 数据。
mutationquerymutation可以把主应用和 Component 的写入放进同一 top-level transaction。
actionquerymutationaction可触达 external I/O 型 Component action,但仍要遵守 action 的幂等与重试边界。

mutation 而言,transaction 行为比“调用了另一个模块”更细致:

  1. top-level mutation 成功时,主应用与所有 Component mutation 的写入一起 commit;top-level mutation 未捕获异常时,相关写入一起 rollback。
  2. 单次 Component mutation 又是独立的 sub-transaction:如果它抛出异常且主应用捕获了异常,只有那次 Component 调用会 rollback,主应用可选择继续其他写入。
  3. 因此,是否捕获 Component 错误是业务语义选择。上面的 throws: true 不捕获,适合“限流失败就拒绝发消息”;开发环境旁路等场景才可能有意识地捕获它。

这不是分布式两阶段提交:它是在 Convex 的同一 transaction 模型内提供的跨 Component 原子性与可捕获的 sub-transaction 行为。Transactions across Components

5. 常见边界与易错点#

主题正确理解实施建议
Id 类型Component boundary 外,Id<"table"> 会变为普通 string;目前不能在一个 Component 中用 v.id() 验证主应用或另一个 Component 的 table。传递跨边界 ID 时把它当 opaque string,在拥有该表的边界内再验证业务归属。
AuthenticationComponent 内没有 ctx.auth在主应用 wrapper 做 authentication/authorization,再显式传入 userId、tenant ID 或经过授权的能力。
Environment variablesComponent 不能任意读取主应用的 process.env;普遍可用的系统变量仅包括 CONVEX_CLOUD_URLCONVEX_SITE_URLComponent 作者在 defineComponent(..., { env }) 声明 typed environment variables;安装方在 app.use(..., { env }) 显式绑定;只在 handler 内读取。
HTTP ActionsComponent 的 http.ts route 默认不暴露。安装时使用 { httpPrefix: "/my-component/" } 才会把 /hello 暴露为 /my-component/hello;不要把 Rust 内部的 mount_path 误写成用户 API。
Component HTTP authenticationComponent HTTP action 同样没有 ctx.auth,也不能读取 app environment variables。需要用户认证或应用数据时,在主应用 convex/http.ts 写受控 wrapper。
Pagination内置 .paginate() 不能直接用于 Components 的 reactive pagination。Component 作者应使用 convex-helpers 的 paginator;需要 React usePaginatedQuery 时也用对应 helper。
回调主应用Component 不能随意调用 parent app function。使用显式的 FunctionHandle;它是可序列化的 reference,但目标 function 仍可能在后续 code push 中被删除。

httpPrefix 是当前公开 TypeScript API 的名字。主应用自己的 defineApp({ httpPrefix }) 不会自动影响 child Component 的 HTTP routes;Component 的 prefix 始终相对于 deployment URL 根路径。未设置 Component httpPrefix 时,Component route 不会占用主应用的 URL 空间。HTTP Routes HTTP Actions

6. 如何从 Directory 选择 Component#

目录提供的是发现入口,不是“所有条目同等经过官方支持”的承诺。选择前可以按如下顺序排查:

  1. 先确认需求是否真的需要独立持久状态:例如 Rate LimiterWorkflowAggregateAgentWorkpool 都是 Component 的典型场景;纯格式化或一次性业务逻辑则不必引入 Component。
  2. 区分社区条目和 Convex 团队条目;团队条目可从 get-convex / @convex-dev 清单核对,但仍要读该项目的 README、release 与 LICENSE。
  3. 阅读 Component 暴露的 function API,特别检查它要求的 env、Function Handle、HTTP route、external webhook/API key 和 migration 方案。
  4. 为 package 和 convex 版本保留 lockfile;升级先在 dev/preview deployment 执行 codegen、typecheck 与针对业务 wrapper 的 integration test。
  5. 在 Dashboard 用 Component selector 观察它独立的数据、functions、files 与 logs;使用 convex-test 时必须注册 Component,许多 package 会提供 /test helper。Testing Components

Components 本身是开源代码和数据,不收取一项单独的“Component 使用费”;但它们会按实现方式消耗对应的 Convex usage,例如 function execution、database、File Storage 或 external service 调用。成本评估应以具体 Component 的数据模型、调用频率和所用套餐为准。Components Directory FAQ

7. 编写自己的 Component:最小结构与责任边界#

当需求确实需要可复用的私有状态时,可以先做 local Component,再决定是否打包发布。官方建议把 local Component 放在 convex/components/,其目录结构与普通 convex/ backend 类似,但拥有自己的 generated code:

convex/components/myComponent/
├── _generated/          # Component 专属 generated code
├── convex.config.ts     # defineComponent 与 child Components
├── schema.ts            # 仅 Component 可访问的 schema
└── lib.ts               # queries / mutations / actions
// convex/components/myComponent/convex.config.ts
import { defineComponent } from "convex/server";

const component = defineComponent("myComponent");

export default component;
// convex/convex.config.ts
import { defineApp } from "convex/server";
import myComponent from "./components/myComponent/convex.config.js";

const app = defineApp();
app.use(myComponent);

export default app;

本地 Component 修改后运行:

npx convex dev --once --typecheck-components

Component 内的 function 定义应从自身./_generated/server.js 导入 builders,而不是误用主应用的 convex/_generated/server.js。它的 public functions 构成 parent 可见的 API;应为它们提供 argsreturns validators。要发布 npm package 时,可从官方模板开始:

npx create-convex@latest --component

打包的 Component 比本地 Component 多一层构建顺序:先对 Component 本身执行 codegen,再构建 npm package,最后由 example app 执行 npx convex dev --typecheck-components。官方 authoring 文档也说明 package 至少需要导出 convex.config.js_generated/component.js 等使用入口。Authoring Components

8. 从源码理解 Components:三个推荐入口#

若希望从使用方式继续深入实现,可按“app facade → Component public API → 私有 schema/算法”的方向读。以下是 2026-08-12 核对的公开源码快照,便于将本文概念落回真实实现:

Component建议入口值得研究的机制
rate-limitersrc/client/index.tssrc/component/lib.tsschema.tsfacade 如何调用 ComponentApi、私有 rateLimits 表、token bucket / fixed window 与分片。
workflowsrc/client/step.tsjournal.tsschema.tsdeterministic replay、持久化 steps/events 与 Workpool 调度。
aggregatesrc/client/index.tspublic.tsbtree.tsprivate B-tree、count/sum/rank,以及为什么主应用必须在同一 mutation 中显式同步 insert/replace/delete

Aggregate 是理解隔离代价的好反例:它不能自动观察主应用表,因此应用的每一条写路径都要显式维护 aggregate;Convex mutation 的原子性保证主表写入和 aggregate 更新一起 commit 或 rollback。不要把“Component 有独立数据”误解为“平台会替你同步任意两张表”。

更底层的 Component 配置、类型检查与 HTTP route 装配,可沿上一篇 Convex 源码导读:开源仓库版图、核心架构与阅读路线 中的 convex-jsconvex-backend 阅读路线继续追踪。

结语#

正确使用 Convex Components 的关键不是“少写代码”,而是先画清数据和权限边界:主应用负责 client-facing API、authentication、authorization 与领域数据;Component 负责它自己封装的状态与算法;两者只用经过验证的 function API、arguments 和必要时的 FunctionHandle 协作。

一旦按这条边界组织,Component 可以同时保留普通 library 的易复用性、外部服务的封装性,以及 Convex transaction 的一致性。反之,若把 components.* 直接当作 browser API、把 Component 当作可以读取所有 app 数据的插件,或忽略版本/许可证/external side effect 审查,就会失去它最有价值的安全和维护优势。

一手资料#

本文共 5971 字,上次修改于 Aug 12, 2026,以 CC 署名-非商业性使用-禁止演绎 4.0 国际 协议进行许可。

相关文章

» Convex 源码导读:开源仓库版图、核心架构与阅读路线

» Convex + TanStack Query:useQuery 与 useSuspenseQuery 的选择、原理与场景

» Cloudflare Computer 技术解析:持久 Workspace、执行后端与 Agent 集成

» TanStack Query v5 基础:从 Server State 到 Query、Mutation 与缓存

» Sentry 配置实践:前后端项目划分、Logs、Tracing、Source Maps 与 CLI 迁移