mcpField
S
schema-builder

schema-builder

github.com/get-convex数据与数据库69545 次安装1 天前更新
代码仓库

关于 schema-builder

schema-builder 是面向 Convex 开发者的模式设计技能,用于创建或调整数据库表结构。它侧重关系建模、索引规划和严格验证器,并提供从嵌套数据迁移到关系结构的指导。

主要功能

  1. 01创建和扩展 Convex 模式
  2. 02设计一对多及多对多关系
  3. 03规划单字段与复合索引
  4. 04使用严格类型验证器
  5. 05将嵌套数据改为关系结构

适用场景

  1. 01新建 convex/schema.ts
  2. 02为现有表添加索引
  3. 03设计用户与项目的数据关系
  4. 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 设计原则

  1. 文档-关系型:使用带 ID 引用的扁平文档,而非深层嵌套
  2. 为外键建立索引:始终为查询中使用的字段建立索引(userId、teamId 等)
  3. 限制数组大小:数组仅用于较小且有界的集合(少于 8192 个条目)
  4. 类型安全:使用严格的 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()

索引策略

  1. 单字段索引:用于简单查询

    • by_user: ["userId"]
    • by_email: ["email"]
  2. 复合索引:用于带筛选条件的查询

    • by_user_and_status: ["userId", "status"]
    • by_team_and_created: ["teamId", "createdAt"]
  3. 移除冗余索引: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