关于 schema-builder
schema-builder 是面向 Convex 开发者的模式设计技能,用于创建或调整数据库表结构。它侧重关系建模、索引规划和严格验证器,并提供从嵌套数据迁移到关系结构的指导。
主要功能
- 01创建和扩展 Convex 模式
- 02设计一对多及多对多关系
- 03规划单字段与复合索引
- 04使用严格类型验证器
- 05将嵌套数据改为关系结构
适用场景
- 01新建 convex/schema.ts
- 02为现有表添加索引
- 03设计用户与项目的数据关系
- 04将嵌套记录拆分为关联表
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ get-convex/ agent-skills --skill schema-builder安装后在 Claude Code 中输入/schema-builder确认已加载。
详细介绍
Convex Schema 构建器
遵循最佳实践,构建结构良好的 Convex Schema,合理设计关系、索引和验证器。
适用场景
- 创建新的
convex/schema.ts文件 - 向现有 Schema 添加表
- 设计数据模型关系
- 添加或优化索引
- 将嵌套数据转换为关系结构
Schema 设计原则
- 文档-关系型:使用带 ID 引用的扁平文档,而非深层嵌套
- 为外键建立索引:始终为查询中使用的字段建立索引(userId、teamId 等)
- 限制数组大小:数组仅用于较小且有界的集合(少于 8192 个条目)
- 类型安全:使用严格的
v.*类型验证器
Schema 模板
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
tableName: defineTable({
// Required fields
field: v.string(),
// Optional fields
optional: v.optional(v.number()),
// Relations (use IDs)
userId: v.id("users"),
// Enums with union + literal
status: v.union(
v.literal("active"),
v.literal("pending"),
v.literal("archived")
),
// Timestamps
createdAt: v.number(),
updatedAt: v.optional(v.number()),
})
// Index for queries by this field
.index("by_user", ["userId"])
// Compound index for common query patterns
.index("by_user_and_status", ["userId", "status"])
// Index for time-based queries
.index("by_created", ["createdAt"]),
});
常见模式
一对多关系
export default defineSchema({
users: defineTable({
name: v.string(),
email: v.string(),
}).index("by_email", ["email"]),
posts: defineTable({
userId: v.id("users"),
title: v.string(),
content: v.string(),
}).index("by_user", ["userId"]),
});
使用连接表实现多对多关系
export default defineSchema({
users: defineTable({
name: v.string(),
}),
projects: defineTable({
name: v.string(),
}),
projectMembers: defineTable({
userId: v.id("users"),
projectId: v.id("projects"),
role: v.union(v.literal("owner"), v.literal("member")),
})
.index("by_user", ["userId"])
.index("by_project", ["projectId"])
.index("by_project_and_user", ["projectId", "userId"]),
});
层级数据
export default defineSchema({
comments: defineTable({
postId: v.id("posts"),
parentId: v.optional(v.id("comments")), // null for top-level
userId: v.id("users"),
text: v.string(),
})
.index("by_post", ["postId"])
.index("by_parent", ["parentId"]),
});
较小的有界数组(可使用)
export default defineSchema({
users: defineTable({
name: v.string(),
// Small, bounded collections are fine
roles: v.array(v.union(
v.literal("admin"),
v.literal("editor"),
v.literal("viewer")
)),
tags: v.array(v.string()), // e.g., max 10 tags
}),
});
验证器参考
// Primitives
v.string()
v.number()
v.boolean()
v.null()
v.id("tableName")
// Optional
v.optional(v.string())
// Union types (enums)
v.union(v.literal("a"), v.literal("b"))
// Objects
v.object({
key: v.string(),
nested: v.number(),
})
// Arrays
v.array(v.string())
// Records (arbitrary keys)
v.record(v.string(), v.boolean())
// Any (avoid if possible)
v.any()
索引策略
-
单字段索引:用于简单查询
by_user: ["userId"]by_email: ["email"]
-
复合索引:用于带筛选条件的查询
by_user_and_status: ["userId", "status"]by_team_and_created: ["teamId", "createdAt"]
-
移除冗余索引:
by_a_and_b通常已涵盖by_a
检查清单
- 所有外键都有索引
- 常见查询模式都有复合索引
- 数组规模较小且有界(或已转换为关系)
- 所有字段都有合适的验证器
- 枚举使用
v.union(v.literal(...))模式 - 时间戳使用
v.number()(自 Unix 纪元以来的毫秒数)
从嵌套结构迁移到关系结构
如果要从嵌套结构进行转换:
之前:
users: defineTable({
posts: v.array(v.object({
title: v.string(),
comments: v.array(v.object({ text: v.string() })),
})),
})
之后:
users: defineTable({
name: v.string(),
}),
posts: defineTable({
userId: v.id("users"),
title: v.string(),
}).index("by_user", ["userId"]),
comments: defineTable({
postId: v.id("posts"),
text: v.string(),
}).index("by_post", ["postId"]),
评论
支持 Markdown