mcpField
C
convex-migrations

convex-migrations

github.com/waynesutton数据与数据库4052.6k 次安装4 周前更新
代码仓库

关于 convex-migrations

为 Convex 项目提供安全的数据迁移指导,涵盖 schema 变更与分批回填。适合需要调整既有文档结构并保持部署可通过验证的开发者。

主要功能

  1. 01六步迁移与部署顺序
  2. 02分批且可重复运行的回填
  3. 03迁移组件进度与状态管理
  4. 04试运行、取消及顺序执行
  5. 05常见 schema 验证错误排查

适用场景

  1. 01为已有表添加必填字段
  2. 02重命名字段或转换字段类型
  3. 03拆分或合并数据表
  4. 04处理超出单次 mutation 限制的回填

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/waynesutton/builder-skills --skill convex-migrations

安装后在 Claude Code 中输入/convex-migrations确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

agents/
openai.yaml
assets/
small-logo.svg
references/
migration-patterns.md
migrations-component.md

详细介绍

Convex 迁移

生成一个架构变更和分批回填,确保每次部署都能顺利完成。唯一的原则:架构必须描述当前实际存在的文档,而不是你期望存在的文档。先变更数据,再收紧验证器。

Convex 没有迁移文件或 migrate 命令。npx convex dev 会推送架构,并根据架构验证每一份现有文档。现有数据不会自动转换。

何时需要使用

  • 添加一个应设为必填的字段,但表中已经有数据
  • 重命名字段、更改字段类型或删除字段
  • 将一张表拆分为两张,或将两张表合并为一张
  • 修改架构后,npx convex dev 报错 Schema validation failed
  • 回填需要处理的行数超过单个 mutation 的处理能力

分步操作示例(重命名、更改类型、拆分、合并、添加必填字段、删除字段),请打开 references/migration-patterns.md。有关安装和运行 @convex-dev/migrations,请打开 references/migrations-component.md。

安全的操作顺序

每次迁移都遵循相同的六个步骤。跳过任何一步都可能导致部署失败。

  1. 在 convex/schema.ts 中将字段设为可选(或将新字段添加为 v.optional)。
  2. 使用 npx convex dev 部署。更新读取逻辑以处理 undefined,并更新写入逻辑以设置新的数据形状。
  3. 使用内部 mutation 或迁移组件,分批回填现有文档。
  4. 将验证器改为最终形状(从 v.optional(v.string()) 改为 v.string(),或删除旧字段)。
  5. 再次部署。此时每份文档都已符合要求,因此验证会通过。
  6. 清理:删除回退代码、移除回填函数、删除过期索引。

步骤 1 和 2 必须先于步骤 3 部署。否则,回填运行时,新写入仍会生成旧的数据形状。

手动编写分批回填

编写一个 internalMutation,使用 paginate 分页遍历表,只修补仍需处理的文档,然后使用继续游标重新安排自身执行。每个批次都是独立事务,因此大型表不会触及单个函数的限制。

// convex/migrations.ts
import { internalMutation } from "./_generated/server";
import { internal } from "./_generated/api";
import { v } from "convex/values";

const BATCH_SIZE = 100;

export const backfillAvatarUrl = internalMutation({
  args: { cursor: v.union(v.string(), v.null()) },
  returns: v.null(),
  handler: async (ctx, args) => {
    const result = await ctx.db
      .query("users")
      .paginate({ numItems: BATCH_SIZE, cursor: args.cursor });

    for (const user of result.page) {
      // Idempotent: skip documents that already have the field
      if (user.avatarUrl === undefined) {
        await ctx.db.patch(user._id, {
          avatarUrl: defaultAvatar(user.name),
        });
      }
    }

    // Reschedule with the cursor until the table is exhausted
    if (!result.isDone) {
      await ctx.scheduler.runAfter(0, internal.migrations.backfillAvatarUrl, {
        cursor: result.continueCursor,
      });
    }
    return null;
  },
});

function defaultAvatar(name: string): string {
  return `https://api.dicebear.com/7.x/initials/svg?seed=${encodeURIComponent(name)}`;
}

使用 CLI 针对开发部署启动:

npx convex run migrations:backfillAvatarUrl '{"cursor": null}'

确保 mutation 是幂等的。部分失败后重新运行必须安全。使用 internalMutation,绝不要使用公开的 mutation,这样就无法从客户端调用。使用 ctx.db.patch 添加或更改字段,使用 ctx.db.patch(id, { field: undefined }) 删除字段。

何时改用组件

@convex-dev/migrations 封装了上述模式,并补充了手动编写版本所缺少的功能:按迁移持久化进度、状态检查、试运行、取消,以及按顺序运行多个迁移。遇到以下情况时,请使用它:

  • 项目在生命周期内会运行不止一两个迁移
  • 你需要确认某个迁移是否已在生产环境完成
  • 你希望在处理真实数据前先试运行
  • 多个迁移必须按固定顺序运行

最简设置:

// convex/convex.config.ts
import { defineApp } from "convex/server";
import migrations from "@convex-dev/migrations/convex.config.js";

const app = defineApp();
app.use(migrations);
export default app;
// convex/migrations.ts
import { Migrations } from "@convex-dev/migrations";
import { components } from "./_generated/api";
import { DataModel } from "./_generated/dataModel";

export const migrations = new Migrations<DataModel>(components.migrations);

export const addDefaultRole = migrations.define({
  table: "users",
  migrateOne: async (ctx, user) => {
    if (user.role === undefined) {
      await ctx.db.patch(user._id, { role: "user" });
    }
  },
});
npx convex run migrations:addDefaultRole '{"dryRun": true}'
npx convex run migrations:addDefaultRole

小型表的一次性回填不需要使用组件。手动编写的 mutation 就够了。

解读部署时的架构错误

架构推送失败时,npx convex dev 会输出表名、一个出错文档的 ID,以及不匹配的字段:

Schema validation failed
Document with ID "j57abc..." in table "users" does not match the schema:
Object is missing the required field `avatarUrl`.
Consider wrapping the field validator in `v.optional(...)` if this is expected.

可以这样理解:users 中的现有数据早于该字段。解决方法绝不是删除文档。将字段包装在 v.optional 中,部署、回填,然后移除 v.optional。如果错误提示字段类型不正确(例如,预期为 number,实际却是 string),请将验证器放宽为 v.union(v.string(), v.number()),回填转换后的数据,然后再收窄验证器。

不要向 defineSchema 传入 { schemaValidation: false } 来让推送成功。这样会掩盖不匹配问题,并让错误转移到查询中。

常见错误

错误问题原因正确做法
在一次部署中添加必填字段现有文档无法通过验证,推送会被拒绝先将其设为 v.optional,回填数据,然后再设为必填
在部署新的写入逻辑之前开始回填新行仍会以旧格式写入先部署架构和代码,再进行回填
使用一个会对整张表执行 .collect() 的 mutation会触及事务大小和时间限制使用 paginate 分页,并按批次重新调度
使用公开的 mutation 回填任何人都可以调用并重新运行使用 internalMutation 或 migrations.define
补丁操作不具备幂等性失败后重新运行会重复应用更改补丁前先检查字段
在读取方停止使用旧字段之前将其删除回退代码会读取到 undefined先切换读取逻辑,再删除旧字段
使用 .filter() 查找尚未迁移的行每批都会执行全表扫描对整张表分页,或使用带索引的 customRange
为了通过部署而关闭 schemaValidation查询在运行时会遇到错误数据修复数据,并保持验证开启
首先对生产环境运行回填无法及时发现转换错误先在开发环境运行,再使用 --prod

检查清单

  • 新增或变更的字段在首次部署时为 v.optional(或放宽后的 v.union)
  • 回填开始前,读取方已处理 undefined,写入方已生成新格式
  • 回填使用 internalMutation 或 migrations.define,绝不公开
  • 回填使用 paginate 分页,并通过 ctx.scheduler.runAfter(0, internal....) 重新调度
  • 每次补丁操作都有保护条件,确保重新运行安全
  • 在处理生产环境之前,已在开发环境完成回填
  • 验证器已切换为最终格式,并已成功部署,没有验证错误
  • 已移除回退代码、旧字段和回填函数
  • 已删除或重命名引用旧字段的索引

文档

评论

支持 Markdown