convex-create-component by get-convex/agent-skills
npx skills add https://github.com/get-convex/agent-skills --skill convex-create-component创建具有清晰边界和精简应用层 API 的可复用 Convex 组件。
convex/ 中的应用层编排逻辑convex.config.ts、schema.ts 和函数文件创建组件结构。./_generated/server 导入来实现函数,而不是应用的生成文件。广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
app.use(...) 将组件接入应用。如果应用尚未拥有 convex/convex.config.ts,请创建它。components.<name>,使用 ctx.runQuery、ctx.runMutation 或 ctx.runAction 从应用调用组件。npx convex dev 并修复代码生成、类型或边界问题。询问用户,然后选择一条路径:
| 目标 | 形态 | 参考文件 |
|---|---|---|
| 仅用于此应用的组件 | 本地组件 | references/local-components.md |
| 跨应用发布或共享 | 打包组件 | references/packaged-components.md |
| 用户明确需要本地 + 共享库代码 | 混合组件 | references/hybrid-components.md |
| 不确定 | 默认使用本地组件 | references/local-components.md |
在继续之前,请精确阅读一个参考文件。
除非用户明确需要 npm 包,否则默认使用本地组件:
convex/components/<componentName>/ 目录下convex.config.ts 中使用 defineComponent(...) 定义convex/convex.config.ts 中使用 app.use(...) 安装npx convex dev 生成组件自身的 _generated/ 文件一个包含一个数据表和两个函数的最小本地组件,以及应用侧的接入代码。
// convex/components/notifications/convex.config.ts
import { defineComponent } from "convex/server";
export default defineComponent("notifications");
// convex/components/notifications/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
notifications: defineTable({
userId: v.string(),
message: v.string(),
read: v.boolean(),
}).index("by_user", ["userId"]),
});
// convex/components/notifications/lib.ts
import { v } from "convex/values";
import { mutation, query } from "./_generated/server.js";
export const send = mutation({
args: { userId: v.string(), message: v.string() },
returns: v.id("notifications"),
handler: async (ctx, args) => {
return await ctx.db.insert("notifications", {
userId: args.userId,
message: args.message,
read: false,
});
},
});
export const listUnread = query({
args: { userId: v.string() },
returns: v.array(
v.object({
_id: v.id("notifications"),
_creationTime: v.number(),
userId: v.string(),
message: v.string(),
read: v.boolean(),
})
),
handler: async (ctx, args) => {
return await ctx.db
.query("notifications")
.withIndex("by_user", (q) => q.eq("userId", args.userId))
.filter((q) => q.eq(q.field("read"), false))
.collect();
},
});
// convex/convex.config.ts
import { defineApp } from "convex/server";
import notifications from "./components/notifications/convex.config.js";
const app = defineApp();
app.use(notifications);
export default app;
// convex/notifications.ts (应用侧包装器)
import { v } from "convex/values";
import { mutation, query } from "./_generated/server";
import { components } from "./_generated/api";
import { getAuthUserId } from "@convex-dev/auth/server";
export const sendNotification = mutation({
args: { message: v.string() },
returns: v.null(),
handler: async (ctx, args) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new Error("Not authenticated");
await ctx.runMutation(components.notifications.lib.send, {
userId,
message: args.message,
});
return null;
},
});
export const myUnread = query({
args: {},
handler: async (ctx) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new Error("Not authenticated");
return await ctx.runQuery(components.notifications.lib.listUnread, {
userId,
});
},
});
注意引用路径的形态:convex/components/notifications/lib.ts 中的函数在应用中被调用为 components.notifications.lib.send。
ctx.auth。process.env。Id 类型在面向应用的 ComponentApi 中会变成普通字符串。v.id("parentTable") 来引用应用拥有的数据表,因为组件无法访问应用的数据表命名空间。./_generated/server 导入 query、mutation 和 action,而不是应用的生成文件。convex/http.ts 中挂载路由,因为组件无法注册自身的 HTTP 路由。convex-helpers 中的 paginator 而不是内置的 .paginate(),因为 .paginate() 无法跨组件边界工作。args 和 returns 验证器,因为组件边界需要明确的类型契约。// 错误:组件代码不能依赖应用的认证或环境变量
const identity = await ctx.auth.getUserIdentity();
const apiKey = process.env.OPENAI_API_KEY;
// 正确:应用解析认证和环境变量,然后传递明确的值
const userId = await getAuthUserId(ctx);
if (!userId) throw new Error("Not authenticated");
await ctx.runAction(components.translator.translate, {
userId,
apiKey: process.env.OPENAI_API_KEY,
text: args.text,
});
// 错误:假设组件函数可以直接被客户端调用
export const send = components.notifications.send;
// 正确:通过应用 mutation 或 query 重新导出
export const sendNotification = mutation({
args: { message: v.string() },
returns: v.null(),
handler: async (ctx, args) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new Error("Not authenticated");
await ctx.runMutation(components.notifications.lib.send, {
userId,
message: args.message,
});
return null;
},
});
// 错误:父级应用数据表的 ID 不是有效的组件验证器
args: { userId: v.id("users") }
// 正确:在边界处将父级拥有的 ID 视为字符串
args: { userId: v.string() }
关于更多模式,包括用于回调的函数句柄、从模式推导验证器、使用全局表的静态配置以及基于类的客户端包装器,请参阅 references/advanced-patterns.md。
按此顺序尝试验证:
npx convex codegen --component-dir convex/components/<name>npx convex codegennpx convex dev重要提示:
CONVEX_DEPLOYMENT 之前,这些命令可能会失败。./_generated/* 导入和应用侧的 components.<name>... 引用将无法通过类型检查。在用户确认目标后,请精确阅读以下文件之一:
references/local-components.mdreferences/packaged-components.mdreferences/hybrid-components.md官方文档:Authoring Components
convex/components/<name>/ 下(或发布时的包布局)./_generated/server 导入v.string() 形式跨边界传递args 和 returns 验证器npx convex dev 并修复了代码生成或类型问题每周安装量
6.7K
仓库
GitHub 星标数
15
首次出现
10 天前
安全审计
安装于
codex6.7K
cursor6.7K
kimi-cli6.7K
amp6.7K
github-copilot6.7K
cline6.7K
Create reusable Convex components with clear boundaries and a small app-facing API.
convex/convex.config.ts, schema.ts, and function files../_generated/server imports, not the app's generated files.app.use(...). If the app does not already have convex/convex.config.ts, create it.components.<name> using ctx.runQuery, ctx.runMutation, or ctx.runAction.npx convex dev and fix codegen, type, or boundary issues before finishing.Ask the user, then pick one path:
| Goal | Shape | Reference |
|---|---|---|
| Component for this app only | Local | references/local-components.md |
| Publish or share across apps | Packaged | references/packaged-components.md |
| User explicitly needs local + shared library code | Hybrid | references/hybrid-components.md |
| Not sure | Default to local | references/local-components.md |
Read exactly one reference file before proceeding.
Unless the user explicitly wants an npm package, default to a local component:
convex/components/<componentName>/defineComponent(...) in its own convex.config.tsconvex/convex.config.ts with app.use(...)npx convex dev generate the component's own _generated/ filesA minimal local component with a table and two functions, plus the app wiring.
// convex/components/notifications/convex.config.ts
import { defineComponent } from "convex/server";
export default defineComponent("notifications");
// convex/components/notifications/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
notifications: defineTable({
userId: v.string(),
message: v.string(),
read: v.boolean(),
}).index("by_user", ["userId"]),
});
// convex/components/notifications/lib.ts
import { v } from "convex/values";
import { mutation, query } from "./_generated/server.js";
export const send = mutation({
args: { userId: v.string(), message: v.string() },
returns: v.id("notifications"),
handler: async (ctx, args) => {
return await ctx.db.insert("notifications", {
userId: args.userId,
message: args.message,
read: false,
});
},
});
export const listUnread = query({
args: { userId: v.string() },
returns: v.array(
v.object({
_id: v.id("notifications"),
_creationTime: v.number(),
userId: v.string(),
message: v.string(),
read: v.boolean(),
})
),
handler: async (ctx, args) => {
return await ctx.db
.query("notifications")
.withIndex("by_user", (q) => q.eq("userId", args.userId))
.filter((q) => q.eq(q.field("read"), false))
.collect();
},
});
// convex/convex.config.ts
import { defineApp } from "convex/server";
import notifications from "./components/notifications/convex.config.js";
const app = defineApp();
app.use(notifications);
export default app;
// convex/notifications.ts (app-side wrapper)
import { v } from "convex/values";
import { mutation, query } from "./_generated/server";
import { components } from "./_generated/api";
import { getAuthUserId } from "@convex-dev/auth/server";
export const sendNotification = mutation({
args: { message: v.string() },
returns: v.null(),
handler: async (ctx, args) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new Error("Not authenticated");
await ctx.runMutation(components.notifications.lib.send, {
userId,
message: args.message,
});
return null;
},
});
export const myUnread = query({
args: {},
handler: async (ctx) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new Error("Not authenticated");
return await ctx.runQuery(components.notifications.lib.listUnread, {
userId,
});
},
});
Note the reference path shape: a function in convex/components/notifications/lib.ts is called as components.notifications.lib.send from the app.
ctx.auth is not available inside components.process.env.Id types become plain strings in the app-facing ComponentApi.v.id("parentTable") for app-owned tables inside component args or schema, because the component has no access to the app's table namespace.query, mutation, and action from the component's own ./_generated/server, not the app's generated files.// Bad: component code cannot rely on app auth or env
const identity = await ctx.auth.getUserIdentity();
const apiKey = process.env.OPENAI_API_KEY;
// Good: the app resolves auth and env, then passes explicit values
const userId = await getAuthUserId(ctx);
if (!userId) throw new Error("Not authenticated");
await ctx.runAction(components.translator.translate, {
userId,
apiKey: process.env.OPENAI_API_KEY,
text: args.text,
});
// Bad: assuming a component function is directly callable by clients
export const send = components.notifications.send;
// Good: re-export through an app mutation or query
export const sendNotification = mutation({
args: { message: v.string() },
returns: v.null(),
handler: async (ctx, args) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new Error("Not authenticated");
await ctx.runMutation(components.notifications.lib.send, {
userId,
message: args.message,
});
return null;
},
});
// Bad: parent app table IDs are not valid component validators
args: { userId: v.id("users") }
// Good: treat parent-owned IDs as strings at the boundary
args: { userId: v.string() }
For additional patterns including function handles for callbacks, deriving validators from schema, static configuration with a globals table, and class-based client wrappers, see references/advanced-patterns.md.
Try validation in this order:
npx convex codegen --component-dir convex/components/<name>npx convex codegennpx convex devImportant:
CONVEX_DEPLOYMENT is configured../_generated/* imports and app-side components.<name>... references will not typecheck.Read exactly one of these after the user confirms the goal:
references/local-components.mdreferences/packaged-components.mdreferences/hybrid-components.mdOfficial docs: Authoring Components
convex/components/<name>/ (or package layout if publishing)./_generated/serverv.string()args and returns validatorsnpx convex dev and fixed codegen or type issuesWeekly Installs
6.7K
Repository
GitHub Stars
15
First Seen
10 days ago
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex6.7K
cursor6.7K
kimi-cli6.7K
amp6.7K
github-copilot6.7K
cline6.7K
99,500 周安装
convex/http.ts, because components cannot register their own HTTP routes.paginator from convex-helpers instead of built-in .paginate(), because .paginate() does not work across the component boundary.args and returns validators to all public component functions, because the component boundary requires explicit type contracts.