目标与何时使用
实体注册仍会为运行时声明表,但真实 schema 演进应走迁移。用于增删改 DDL、重命名、索引、约束,以及必须随插件升级只运行一次的数据回填。
- 预发 / 生产 — 不要依赖 synchronize
- 可回滚变更 — 保持 up() 与 down() 成对
- 已发布文件 — 不要修改;新增带编号的迁移
1. 前置条件
需要在 backend/src/index.ts 中有默认导出的 IPlugin,并明确目标数据源(通常是共享默认)。
- backend/src/index.ts 导出实现 IPlugin 的插件类
- 明确变更所属连接 — 通常为 { plugin: "default", name: "default" }
- 实体定义将更新为与最终迁移 schema 一致
- 编号约定 — backend/src/migrations/ 下的 001、002、…
2. 目录布局
将迁移类放在 backend/src/migrations/,并导出以便 getMigrations() 返回它们。
- 每文件一类;保持名称 / 编号有序
- 若插件使用 ESM,从 index 用 .js 扩展名导入
TSlayout
1backend/
2 src/
3 index.ts # IPlugin — getMigrations()
4 migrations/
5 001-initial.migration.ts
6 002-add-status.migration.ts
7 feature/
8 my.module.ts3. 实现 IDatabaseMigration
实现 getName()、getSource()、up(QueryRunner)、down(QueryRunner)。用 metadata.name 为 getName() 加前缀以跨插件唯一。getSource() 通常返回 { plugin: "default", name: "default" }。
- getName() — 唯一 id;以 metadata.name 为前缀
- getSource() — 执行 SQL 的 DataSource(多数插件用共享默认库)
- up() — 应用变更;本地重跑时可优先 IF EXISTS / IF NOT EXISTS
- down() — 撤销同一变更
TSXmigration class
1import type { IDatabaseMigration } from "@quan-erp/shared-types";
2import type { QueryRunner } from "typeorm";
3import metadata from "../../module.metadata.json" with { type: "json" };
4
5export class AddStatusMigration implements IDatabaseMigration {
6 getName() {
7 return `${metadata.name}_002_add_status`;
8 }
9
10 getSource() {
11 return { plugin: "default", name: "default" };
12 }
13
14 async up(queryRunner: QueryRunner): Promise<void> {
15 await queryRunner.query(
16 `ALTER TABLE ${metadata.name}_order ADD COLUMN IF NOT EXISTS status varchar(32)`,
17 );
18 }
19
20 async down(queryRunner: QueryRunner): Promise<void> {
21 await queryRunner.query(
22 `ALTER TABLE ${metadata.name}_order DROP COLUMN IF EXISTS status`,
23 );
24 }
25}4. 通过 getMigrations() 注册
在 backend/src/index.ts 的默认导出插件类中,由 getMigrations() 返回迁移类。可用可选的 onMigrate() 做迁移后种子或修复。
- 数组顺序应与预期应用顺序一致
- onMigrate() 在待执行迁移批次完成后运行
- 自定义数据源 — getSource() 须匹配该连接的 plugin + name
TSXbackend/src/index.ts
1import type { GetMigrationsType, IPlugin } from "@quan-erp/shared-types";
2import { InitialMigration } from "./migrations/001-initial.migration.js";
3import { AddStatusMigration } from "./migrations/002-add-status.migration.js";
4
5export default class MyPlugin implements IPlugin {
6 getMigrations(): GetMigrationsType {
7 return [InitialMigration, AddStatusMigration];
8 }
9
10 async onMigrate(): Promise<void> {
11 // optional: seed / repair after migrations apply
12 }
13
14 // … getRootModule, getName, getVersion, getMetadata, …
15}5. 验证与规则
安装或升级插件时,核心读取 getMigrations()、应用未执行的名称,并在模块初始化前调用 onMigrate()。
- 未执行的 getName() 会在 getSource() 上运行 up()
- 实体列 / 索引与最终迁移 schema 一致
- 需要回滚时在本地 / 预发测试 down()
- 不要修改已发布迁移 — 新增 003-…
- 真实升级优先迁移而非 synchronize
- 01
安装 / 升级插件
核心加载 module.js 并读取 getMigrations()。
- 02
应用未执行迁移
每个尚未用过的 getName() 会在 getSource() 上执行 up(queryRunner)。
- 03
onMigrate()
迁移批次完成后的可选插件钩子。
- 04
模块初始化
实体、DI 与 @OnInit 照常继续。
常见错误
- 在 IDatabaseMigration 上用普通 name 属性而不是 getName() / getSource()
- 忘记用 metadata.name 为 getName() 加前缀(跨插件冲突)
- getSource() 错误 — 迁移跑在与实体不同的 DataSource 上
- 修改已发布迁移而不是新增编号文件
- 只改实体不写迁移(或相反)
- 生产升级依赖 synchronize: true