国家移徙部分
定义和运行迁移, 如此设定用户的默认值 :
export const setDefaultValue = migrations.define({
table: "users",
migrateOne: async (ctx, user) => {
if (user.optionalField === undefined) {
await ctx.db.patch(user._id, { optionalField: "default" });
}
},
});
然后可以从CLI或程序上直接运行. 见 下级。 。 。 .
npx convex run migrations:setDefaultValue
迁移允许您定义表格中所有文档上的函数 (或指定的子集). 它们分批同步运行(在线迁移).
组件跟踪迁移状态, 避免运行两次, 抓取 (如果在路上发生错误或故障) 通过 Convex 查询实时迁移状态.
见 迁移入门员额 为在线对离线迁移的概念概述。 如果你的迁徙是 小事一桩,你走得很快,还检查一下 仪表板中的轻量级迁移。 。 。 .
移徙的典型步骤:
- 修改您的方案以允许新旧值。 通常这是添加一个 新建可选字段或将一个字段标为可选字段,以便删除。 作为 部分内容,更新您的代码处理两个版本.
- 定义将数据转换到新方案.
- 推动迁移和计划变化.
- 运行迁移到完成 .
- 修改您的策略和代码以承担新值 。 推动这一变化 只有在所有数据都符合新方案时才会成功。 这是默认 convex的行为,除非您禁用计划验证 .
见 此堆栈帖 用于普通使用案例.
预先要求: Convex
你需要一个现有的 Convex 项目来使用组件。 Convex 是一个主机 后端平台,包括数据库、无服务器功能和多一个吨 可以学习 这里。 。 。 .
运行 npm create convex 或遵循其中任何一条
快速启动 来设置一个.
安装
安装组件包 :
npm install @convex-dev/migrations
创建一个 convex.config.ts 应用程序中的文件 convex/ 文件夹并安装
通过调用 use数字 :
// 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。这不是一个
页:1 如果您想要使用不同的文件, 请务必更改
下面的例子 internal.migrations.* 到您的新文件名, 如
internal.myFolder.myMigrationsFile.* 或 CLI 参数类 migrations:* 改为
myFolder/myMigrationsFile:*。 。 。 .
import { Migrations } from "@convex-dev/migrations";
import { components } from "./_generated/api.js";
import schema from "./_generated/schema.js";
export const migrations = new Migrations(components.migrations, { schema });
这个 schema 为移徙定义和支持提供类型安全
在自定义索引上加页
customRange。 。 。 .
与往常一样,迁移中的数据库操作将得到遵守
根据您运行时的策略定义. 说明: 如果您使用的话
自定义函数 要覆盖
internalMutation,参见
下级。 。 。 .
定义迁移
内部 migrateOne 函数,您可以写入代码来修改单个文档
在指定的表格中。 改变是可选的,你也可以阅读
写入此函数的其他表格.
export const setDefaultValue = migrations.define({
table: "myTable",
migrateOne: async (ctx, doc) => {
if (doc.optionalField === undefined) {
await ctx.db.patch(doc._id, { optionalField: "default" });
}
},
});
短期语法
由于最常见的迁移包括补丁每个文档,如果返回 对象,将自动作为补丁应用.
export const clearField = migrations.define({
table: "myTable",
migrateOne: () => ({ optionalField: undefined }),
});
// is equivalent to `await ctx.db.patch(doc._id, { optionalField: undefined })`
使用索引移动表格的子集
如果您想要迁移一系列文档, 您可以避免处理
表格全文,以指定 customRange。您可以使用任何已有索引
或内置 by_creation_time 指数.
import { Migrations } from "@convex-dev/migrations";
import { components } from "./_generated/api.js";
import schema from "./_generated/schema.js";
export const migrations = new Migrations(components.migrations, { schema });
export const validateRequiredField = migrations.define({
table: "myTable",
customRange: (query) =>
query.withIndex("by_requiredField", (q) => q.eq("requiredField", "")),
migrateOne: async (_ctx, doc) => {
console.log("Needs fixup: " + doc._id);
// Shorthand for patching
return { requiredField: "<unknown>" };
},
});
注意: 要使用自定义 Range, 您必须向移民提供您的计划 .
一次进行一次迁移
使用 Dashboard 或 CLI
管理移民的最简单方式是直接称呼:
npx convex run migrations:setDefaultValue
过 --prod 用于生产.
在仪表板上运行:

您也可以定义接受迁移的通用跑道:
export const run = migrations.runner();
那就用那个 函数名称数字 :
npx convex run migrations:run '{fn: "migrations:setDefaultValue"}'
见 下级 以通过的方式 setDefaultValue。 。 。 .
程序
也可以运行来自其他 Convex 变异或动作的迁移 :
await migrations.runOne(ctx, internal.example.setDefaultValue);
行为
- 如果它已经在运行,它会拒绝再开始一个重复的工人.
- 如果它以前在一些批次上失败了,它将继续从该批次开始
除非您手动指定
cursor或者说reset。 。 。 . - 如果你提供明确
cursor(单位:千美元)null表示从头开始, 从那里开始. - 如果你通过了
reset: true,它会重新开始迁移 该组的所有迁移(包括任何迁移)next移民问题). - 如果你通过了
true页:1dryRun然后它会运行一批 然后扔,所以 你可以看到它会做什么。 见 下级 这对验证它有用 你所期望的.
连续进行迁移
可以按顺序进行一系列迁移. 如果有迁移的话, 这样做是有用的 取决于先前的,或者如果您保留所有迁移的运行列表 下个部署就应该开始.
使用 Dashboard 或 CLI
您可以通过迁移列表到 runner 让它运行一系列
移徙,而不只是一次:
export const runAll = migrations.runner([
internal.migrations.setDefaultValue,
internal.migrations.validateRequiredField,
internal.migrations.convertUnionField,
]);
然后就运行:
npx convex run migrations:runAll # migrations:runAll is equivalent to convex/migrations.ts:runAll on the CLI
直接拨打移民电话时,或与 runner 函数,您可以通过
一个"next"的参数在第一次迁移后运行一系列迁移:
npx convex run migrations:setDefaultValue '{next:["migrations:clearField"]}'
# OR
npx convex run migrations:run '{fn: "migrations:setDefaultValue", next:["migrations:clearField"]}'
程序
await migrations.runSerially(ctx, [
internal.migrations.setDefaultValue,
internal.migrations.validateRequiredField,
internal.migrations.convertUnionField,
]);
行为
- 如果在试图迁移时已经在进行中,那就行不通.
- 如果迁移已经完成,它将跳过它.
- 如果移徙有部分进展,移徙将恢复原状.
- 如果一次迁移失败或被取消,它将不再继续,如果您有的话 移民之间的一些依赖关系。 再次调用系列重试.
注意:如果开始多次串行迁移,行为是:
- 如果它们在功能上不重叠,它们就会快乐地平行运行.
- 如果他们有一个共同的功能,一个在另一个尝试之前完成 它,第二会只是跳过它.
- 如果它们有一个共同的功能,一个正在进行,第二个将 不再继续移动.
业务
用干流测试迁移
在运行可能不可逆转地更改数据的迁移之前,您可以验证一个
批次通过 dryRun 对任意 runner 或者说 runOne 命令 :
npx convex run migrations:setDefaultValue '{dryRun: true}'
重新开始迁移
过 reset: true 迫使移民从头开始.
npx convex run migrations:setDefaultValue '{reset: true}'
如果您指定 next 或运行定义的迁移序列,这将重设
光标指向组中的所有迁移.
npx convex run migrations:runAll '{reset: true}'
您也可以从任何有效的光标中通过。 您可以找到有效的光标
答复 getStatus。这可以允许重试迁移
从一个已知的好点, 当你在代码上.
注意:如果您正在从 CLI 的特定光标开始迁移,或者
仪表板,不要通过 dryRun: false 明确,不要定义它.
停止迁移
您可以停止 CLI 或仪表板的迁移, 调用组件 API 直接 :
npx convex run --component migrations lib:cancel '{name: "migrations:myMigration"}'
或者通过 migrations.cancel 程序上.
await migrations.cancel(ctx, internal.migrations.myMigration);
获取移民状况
要了解移民的动态状况,你可以通过 CLI: 爱丽丝
npx convex run --component migrations lib:getStatus --watch
这个 --watch 随时更新状态 或在方案方面:
const status: MigrationStatus[] = await migrations.getStatus(ctx, {
limit: 10,
});
// or
const status: MigrationStatus[] = await migrations.getStatus(ctx, {
migrations: [
internal.migrations.setDefaultValue,
internal.migrations.validateRequiredField,
internal.migrations.convertUnionField,
],
});
该类型被注释为避免循环类型依赖, 例如, 如果您 正在返回一个查询的结果,该查询在同一个文件中被定义 引用的迁移.
作为生产部署的一部分进行迁移
作为构建和部署命令的一部分,你可以将相应的命令链条链条
npx convex run 命令,例如:
npx convex deploy --cmd 'npm run build' && npx convex run convex/migrations.ts:runAll --prod
配置选项
覆盖内部Mutation以应用自定义 DB 行为
您可以自定义哪个 internalMutation b. 减少移徙
应用.
如果你用的话,这可能很重要 自定义函数 要拦截 数据库写入以应用验证或 更改时触发操作。 。 。 .
假设您定义自己的 internalMutation 内 convex/functions.ts数字 :
import { internalMutation } from "./functions";
import { Migrations } from "@convex-dev/migrations";
import { components } from "./_generated/api";
export const migrations = new Migrations(components.migrations, {
internalMutation,
});
见 本条 页:1 关于使用和先进模式的更多信息.
自定义批量大小
该组件将获取您的数据, 分批为100, 并调用您的功能 每批文档。 如果您想要更改批量大小, 您可以指定 这个 如果您的文档是大的, 这会有所帮助, 以避免运行在 交易限制, (中文(简体) ), 或文档经常更新且您正在看到OCC 冲突 在迁徙时.
export const clearField = migrations.define({
table: "myTable",
batchSize: 10,
migrateOne: () => ({ optionalField: undefined }),
});
您也可以在单个引用时覆盖此批量大小 :
await migrations.runOne(ctx, internal.migrations.clearField, {
batchSize: 1,
});
平行批次
每个批次都是按序处理的,但是在一个批次内,你可以拥有每个批次
migrateOne 如果通过, 呼叫并行运行 parallelize: true如果你这样做,
保证你的回话不会认为每个电话都是孤立的 例如,如果
每次通话读取后都会更新相同的计数器,然后在
同样的批量可以读取相同的反相值,然后从一个上下车. 结果,
迁移在默认情况下进行.
export const clearField = migrations.define({
table: "myTable",
parallelize: true,
migrateOne: () => ({ optionalField: undefined }),
});
短手运行语法 :
对不想打字的人来说 migrations:myNewMigration 每一次
他们从CLI或仪表盘上运行一个迁移,特别是如果你定义你的
其他地方的移民情况 ops/db/migrations:myNewMigration,您可以配置
前缀 :
export const migrations = new Migrations(components.migrations, {
internalMigration,
migrationsLocationPrefix: "migrations:",
});
然后打电话给:
npx convex run migrations:run '{fn: "myNewMutation", next: ["myNextMutation"]}'
或用代码 :
await migrations.getStatus(ctx, { migrations: ["myNewMutation"] });
await migrations.cancel(ctx, "myNewMutation");
同步进行迁移
如果您想要从测试或动作同步运行迁移, 您可以使用
runToCompletion。请注意,如果动作崩溃或被取消,则不会
在背景中继续迁徙.
从动作 :
import { components, internal } from "./_generated/api";
import { internalAction } from "./_generated/server";
import { runToCompletion } from "@convex-dev/migrations";
export const myAction = internalAction({
args: {},
handler: async (ctx) => {
//...
const toRun = internal.example.setDefaultValue;
await runToCompletion(ctx, components.migrations, toRun);
},
});
在测试中:
import { test } from "vitest";
import { convexTest } from "convex-test";
import component from "@convex-dev/migrations/test";
import { runToCompletion } from "@convex-dev/migrations";
import { components, internal } from "./_generated/api";
import schema from "./schema";
test("test setDefaultValue migration", async () => {
const t = convexTest(schema);
// Register the component in the test instance
component.register(t);
await t.run(async (ctx) => {
// Add sample data to migrate
await ctx.db.insert("myTable", { optionalField: undefined });
// Run the migration to completion
const migrationToTest = internal.example.setDefaultValue;
await runToCompletion(ctx, components.migrations, migrationToTest);
// Assert that the migration was successful by checking the data
const docs = await ctx.db.query("myTable").collect();
expect(docs.every((doc) => doc.optionalField !== undefined)).toBe(true);
});
});