schema-builder
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseConvex Schema Builder
Convex 模式构建工具
Build well-structured Convex schemas following best practices for relationships, indexes, and validators.
遵循关系、索引和验证器的最佳实践,构建结构合理的Convex模式。
When to Use
使用场景
- Creating a new file
convex/schema.ts - Adding tables to existing schema
- Designing data model relationships
- Adding or optimizing indexes
- Converting nested data to relational structure
- 创建新的文件
convex/schema.ts - 向现有模式中添加表
- 设计数据模型关系
- 添加或优化索引
- 将嵌套数据转换为关系型结构
Schema Design Principles
模式设计原则
- Document-Relational: Use flat documents with ID references, not deep nesting
- Index Foreign Keys: Always index fields used in lookups (userId, teamId, etc.)
- Limit Arrays: Only use arrays for small, bounded collections (<8192 items)
- Type Safety: Use strict validators with types
v.*
- 文档-关系型: 使用带有ID引用的扁平文档,而非深度嵌套
- 索引外键: 始终为用于查询的字段(如userId、teamId等)创建索引
- 限制数组使用: 仅将数组用于小型、有限的集合(少于8192个元素)
- 类型安全: 使用类型的严格验证器
v.*
Schema Template
模式模板
typescript
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"]),
});typescript
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
tableName: defineTable({
// 必填字段
field: v.string(),
// 可选字段
optional: v.optional(v.number()),
// 关系(使用ID)
userId: v.id("users"),
// 枚举类型(联合+字面量)
status: v.union(
v.literal("active"),
v.literal("pending"),
v.literal("archived")
),
// 时间戳
createdAt: v.number(),
updatedAt: v.optional(v.number()),
})
// 按该字段查询的索引
.index("by_user", ["userId"])
// 针对常见查询模式的复合索引
.index("by_user_and_status", ["userId", "status"])
// 基于时间查询的索引
.index("by_created", ["createdAt"]),
});Common Patterns
常见模式
One-to-Many Relationship
一对多关系
typescript
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"]),
});typescript
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"]),
});Many-to-Many with Junction Table
多对多关系(使用关联表)
typescript
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"]),
});typescript
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"]),
});Hierarchical Data
层级数据
typescript
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"]),
});typescript
export default defineSchema({
comments: defineTable({
postId: v.id("posts"),
parentId: v.optional(v.id("comments")), // 顶级评论为null
userId: v.id("users"),
text: v.string(),
})
.index("by_post", ["postId"])
.index("by_parent", ["parentId"]),
});Small Bounded Arrays (OK to use)
小型有限数组(允许使用)
typescript
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
}),
});typescript
export default defineSchema({
users: defineTable({
name: v.string(),
// 小型、有限的集合是可行的
roles: v.array(v.union(
v.literal("admin"),
v.literal("editor"),
v.literal("viewer")
)),
tags: v.array(v.string()), // 例如:最多10个标签
}),
});Validator Reference
验证器参考
typescript
// 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()typescript
// 基本类型
v.string()
v.number()
v.boolean()
v.null()
v.id("tableName")
// 可选类型
v.optional(v.string())
// 联合类型(枚举)
v.union(v.literal("a"), v.literal("b"))
// 对象类型
v.object({
key: v.string(),
nested: v.number(),
})
// 数组类型
v.array(v.string())
// 记录类型(任意键)
v.record(v.string(), v.boolean())
// 任意类型(尽可能避免使用)
v.any()Index Strategy
索引策略
-
Single-field indexes: For simple lookups
by_user: ["userId"]by_email: ["email"]
-
Compound indexes: For filtered queries
by_user_and_status: ["userId", "status"]by_team_and_created: ["teamId", "createdAt"]
-
Remove redundant:usually covers
by_a_and_bby_a
-
单字段索引: 用于简单查询
by_user: ["userId"]by_email: ["email"]
-
复合索引: 用于带过滤条件的查询
by_user_and_status: ["userId", "status"]by_team_and_created: ["teamId", "createdAt"]
-
移除冗余索引:通常可以覆盖
by_a_and_b的查询需求by_a
Checklist
检查清单
- All foreign keys have indexes
- Common query patterns have compound indexes
- Arrays are small and bounded (or converted to relations)
- All fields have proper validators
- Enums use pattern
v.union(v.literal(...)) - Timestamps use (milliseconds since epoch)
v.number()
- 所有外键都已创建索引
- 常见查询模式都有对应的复合索引
- 数组仅用于小型有限集合(或已转换为关系型结构)
- 所有字段都配置了适当的验证器
- 枚举类型使用模式
v.union(v.literal(...)) - 时间戳使用(纪元以来的毫秒数)
v.number()
Migration from Nested to Relational
从嵌套结构迁移到关系型结构
If converting from nested structures:
Before:
typescript
users: defineTable({
posts: v.array(v.object({
title: v.string(),
comments: v.array(v.object({ text: v.string() })),
})),
})After:
typescript
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"]),如果需要从嵌套结构转换:
转换前:
typescript
users: defineTable({
posts: v.array(v.object({
title: v.string(),
comments: v.array(v.object({ text: v.string() })),
})),
})转换后:
typescript
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"]),