关于 convex-helpers-guide
convex-helpers-guide 是介绍 convex-helpers 工具集的开发技能,面向构建 Convex 后端的开发者。内容涵盖访问控制、关系查询、会话、验证、迁移和触发器等常见模式。
主要功能
- 01通过自定义函数封装查询与权限逻辑
- 02遍历表关系并加载关联数据
- 03提供复杂过滤和 Zod 验证
- 04支持匿名会话、数据迁移与触发器
适用场景
- 01为多租户后端添加访问控制
- 02查询关联表并构造嵌套数据
- 03追踪未登录用户的会话
- 04迁移数据或响应记录变更
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ get-convex/ agent-skills --skill convex-helpers-guide安装后在 Claude Code 中输入/convex-helpers-guide确认已加载。
详细介绍
Convex Helpers 指南
使用 convex-helpers 为 Convex 后端添加常见模式和实用工具,无需从头重复造轮子。
什么是 convex-helpers?
convex-helpers 是 Convex 官方推出的一套实用工具,可为 Convex 提供补充功能。它提供了经过实战检验的模式,适用于常见的后端需求。
安装:
npm install convex-helpers
可用的 Helpers
1. 关系 Helpers
以清晰、类型安全的方式遍历表之间的关系。
适用场景:
- 加载多个表中的关联数据
- 遍历外键关系
- 构建嵌套数据结构
示例:
import { getOneFrom, getManyFrom } from "convex-helpers/server/relationships";
export const getTaskWithUser = query({
args: { taskId: v.id("tasks") },
handler: async (ctx, args) => {
const task = await ctx.db.get(args.taskId);
if (!task) return null;
// Get related user
const user = await getOneFrom(
ctx.db,
"users",
"by_id",
task.userId,
"_id"
);
// Get related comments
const comments = await getManyFrom(
ctx.db,
"comments",
"by_task",
task._id,
"taskId"
);
return { ...task, user, comments };
},
});
主要函数:
getOneFrom- 获取单个关联文档getManyFrom- 获取多个关联文档getManyVia- 通过联结表获取多对多关系
2. 自定义函数(数据保护)- 最重要
这是 Convex 对行级安全(RLS)的替代方案。 不使用数据库级策略,而是使用自定义函数包装器,为所有查询和变更自动添加身份验证和访问控制。
创建带有自定义行为的查询/变更/操作包装版本。
适用场景:
- 数据保护和访问控制(主要用途)
- 希望为所有函数添加身份验证逻辑
- 多租户应用
- 基于角色的访问控制(RBAC)
- 需要向 ctx 注入通用数据
- 构建仅限内部使用的函数
- 为所有函数添加日志记录/监控
为什么使用它而不是 RLS:
- 使用 TypeScript,而非 SQL 策略
- 完全类型安全
- 易于测试和调试
- 比数据库策略更灵活
- 可在整个后端中使用
示例:自动进行身份验证的自定义查询
// convex/lib/customFunctions.ts
import { customQuery } from "convex-helpers/server/customFunctions";
import { query } from "../_generated/server";
export const authenticatedQuery = customQuery(
query,
{
args: {}, // No additional args required
input: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error("Not authenticated");
}
const user = await ctx.db
.query("users")
.withIndex("by_token", q =>
q.eq("tokenIdentifier", identity.tokenIdentifier)
)
.unique();
if (!user) throw new Error("User not found");
// Add user to context
return { ctx: { ...ctx, user }, args };
},
}
);
// Usage in your functions
export const getMyTasks = authenticatedQuery({
handler: async (ctx) => {
// ctx.user is automatically available!
return await ctx.db
.query("tasks")
.withIndex("by_user", q => q.eq("userId", ctx.user._id))
.collect();
},
});
示例:多租户数据保护
import { customQuery } from "convex-helpers/server/customFunctions";
import { query } from "../_generated/server";
// Organization-scoped query - automatic access control
export const orgQuery = customQuery(query, {
args: { orgId: v.id("organizations") },
input: async (ctx, args) => {
const user = await getCurrentUser(ctx);
// Verify user is a member of this organization
const member = await ctx.db
.query("organizationMembers")
.withIndex("by_org_and_user", q =>
q.eq("orgId", args.orgId).eq("userId", user._id)
)
.unique();
if (!member) {
throw new Error("Not authorized for this organization");
}
// Inject org context
return {
ctx: {
...ctx,
user,
orgId: args.orgId,
role: member.role
},
args
};
},
});
// Usage - data automatically scoped to organization
export const getOrgProjects = orgQuery({
args: { orgId: v.id("organizations") },
handler: async (ctx) => {
// ctx.user and ctx.orgId automatically available and verified!
return await ctx.db
.query("projects")
.withIndex("by_org", q => q.eq("orgId", ctx.orgId))
.collect();
},
});
示例:基于角色的访问控制
import { customMutation } from "convex-helpers/server/customFunctions";
import { mutation } from "../_generated/server";
export const adminMutation = customMutation(mutation, {
args: {},
input: async (ctx, args) => {
const user = await getCurrentUser(ctx);
if (user.role !== "admin") {
throw new Error("Admin access required");
}
return { ctx: { ...ctx, user }, args };
},
});
// Usage - only admins can call this
export const deleteUser = adminMutation({
args: { userId: v.id("users") },
handler: async (ctx, args) => {
// Only admins reach this code
await ctx.db.delete(args.userId);
},
});
3. 筛选 Helper
对数据库查询应用复杂的 TypeScript 筛选条件。
适用场景:
- 需要按计算值筛选
- 筛选逻辑过于复杂,无法通过索引实现
- 处理较小的结果集
示例:
import { filter } from "convex-helpers/server/filter";
export const getActiveTasks = query({
handler: async (ctx) => {
const now = Date.now();
const threeDaysAgo = now - 3 * 24 * 60 * 60 * 1000;
return await filter(
ctx.db.query("tasks"),
(task) =>
!task.completed &&
task.createdAt > threeDaysAgo &&
task.priority === "high"
).collect();
},
});
注意: 尽可能优先使用索引!对于无法通过索引实现的复杂逻辑,请使用筛选功能。
4. 会话
即使用户尚未登录,也能在多个请求中追踪用户。
适用场景:
- 需要追踪匿名用户
- 为访客构建购物车
- 在用户注册前追踪其行为
- 在未进行身份验证的情况下进行 A/B 测试
设置:
// convex/sessions.ts
import { SessionIdArg } from "convex-helpers/server/sessions";
import { query } from "./_generated/server";
export const trackView = query({
args: {
...SessionIdArg, // Adds sessionId: v.string()
pageUrl: v.string(),
},
handler: async (ctx, args) => {
await ctx.db.insert("pageViews", {
sessionId: args.sessionId,
pageUrl: args.pageUrl,
timestamp: Date.now(),
});
},
});
客户端(React):
import { useSessionId } from "convex-helpers/react/sessions";
import { useQuery } from "convex/react";
import { api } from "../convex/_generated/api";
function MyComponent() {
const sessionId = useSessionId();
// Automatically includes sessionId in all requests
useQuery(api.sessions.trackView, {
sessionId,
pageUrl: window.location.href,
});
}
5. Zod 验证
使用 Zod schema,而不是 Convex 验证器。
适用场景:
- 项目中已经使用 Zod
- 需要更复杂的验证逻辑
- 需要自定义错误消息
示例:
import { zCustomQuery } from "convex-helpers/server/zod";
import { z } from "zod";
import { query } from "./_generated/server";
const argsSchema = z.object({
email: z.string().email(),
age: z.number().min(18).max(120),
});
export const createUser = zCustomQuery(query, {
args: argsSchema,
handler: async (ctx, args) => {
// args is typed from Zod schema
return await ctx.db.insert("users", args);
},
});
6. 替代方案:行级安全 Helper
注意: Convex 建议优先使用自定义函数(见上文 #2)作为主要的数据保护模式。此 RLS Helper 是另一种方案,模拟传统的 RLS。
不过,自定义函数通常更好,因为它们:
- 在编译时类型安全(RLS 在运行时执行)
- 更明确(便于查看应用了哪些身份验证逻辑)
- 错误消息更好
- 更易于测试
7. 迁移
安全地运行数据迁移。
适用场景:
- 为新字段回填数据
- 转换现有数据
- 在不同 schema 版本之间迁移
示例:
import { makeMigration } from "convex-helpers/server/migrations";
export const addDefaultPriority = makeMigration({
table: "tasks",
migrateOne: async (ctx, doc) => {
if (doc.priority === undefined) {
await ctx.db.patch(doc._id, { priority: "medium" });
}
},
});
// Run: npx convex run migrations:addDefaultPriority
8. 触发器
在数据变更时自动执行代码。
适用场景:
- 在数据变更时发送通知
- 更新关联记录
- 记录变更
- 维护计算字段
示例:
import { Triggers } from "convex-helpers/server/triggers";
const triggers = new Triggers();
triggers.register("tasks", "insert", async (ctx, task) => {
// Send notification when task is created
await ctx.db.insert("notifications", {
userId: task.userId,
type: "task_created",
taskId: task._id,
});
});
常见模式
模式 1:携带用户上下文的已验证查询
import { customQuery } from "convex-helpers/server/customFunctions";
export const authedQuery = customQuery(query, {
args: {},
input: async (ctx, args) => {
const user = await getCurrentUser(ctx);
return { ctx: { ...ctx, user }, args };
},
});
// Now all queries automatically have user in context
export const getMyData = authedQuery({
handler: async (ctx) => {
// ctx.user is typed and available!
return await ctx.db
.query("data")
.withIndex("by_user", q => q.eq("userId", ctx.user._id))
.collect();
},
});
模式 2:加载关联数据
import { getOneFrom, getManyFrom } from "convex-helpers/server/relationships";
export const getPostWithDetails = query({
args: { postId: v.id("posts") },
handler: async (ctx, args) => {
const post = await ctx.db.get(args.postId);
if (!post) return null;
const author = await getOneFrom(ctx.db, "users", "by_id", post.authorId, "_id");
const comments = await getManyFrom(ctx.db, "comments", "by_post", post._id, "postId");
const tagLinks = await getManyFrom(ctx.db, "postTags", "by_post", post._id, "postId");
const tags = await Promise.all(
tagLinks.map(link =>
getOneFrom(ctx.db, "tags", "by_id", link.tagId, "_id")
)
);
return { ...post, author, comments, tags };
},
});
模式 3:带错误处理的批量操作
import { asyncMap } from "convex-helpers";
export const batchUpdateTasks = mutation({
args: {
taskIds: v.array(v.id("tasks")),
status: v.string(),
},
handler: async (ctx, args) => {
const results = await asyncMap(args.taskIds, async (taskId) => {
try {
const task = await ctx.db.get(taskId);
if (task) {
await ctx.db.patch(taskId, { status: args.status });
return { success: true, taskId };
}
return { success: false, taskId, error: "Not found" };
} catch (error) {
return { success: false, taskId, error: error.message };
}
});
return results;
},
});
何时使用什么
| 需求 | 使用 | 导入自 |
|---|---|---|
| 加载相关数据 | getOneFrom, getManyFrom | convex-helpers/server/relationships |
| 在所有函数中进行身份验证 | customQuery | convex-helpers/server/customFunctions |
| 复杂筛选 | filter | convex-helpers/server/filter |
| 匿名用户 | useSessionId | convex-helpers/react/sessions |
| Zod 验证 | zCustomQuery | convex-helpers/server/zod |
| 数据迁移 | makeMigration | convex-helpers/server/migrations |
| 触发器 | Triggers | convex-helpers/server/triggers |
检查清单
- 已安装 convex-helpers:
npm install convex-helpers - 使用关系辅助工具处理相关数据
- 为常见身份验证模式创建自定义函数
- 使用会话跟踪匿名用户(如有需要)
- 尽可能优先使用索引,而非 filter
- 查看 convex-helpers 文档,了解新工具