何时使用迁移
实体注册仍会为运行时声明表,但真实 schema 演进请优先用迁移。生产升级不要依赖 synchronize。
- 增删改 schema(列、索引、约束、重命名)
- 必须随插件升级只运行一次的数据回填
- 需要在 staging / 生产可回滚的变更
目录布局
迁移类放在 backend/src/migrations/(或 backend/migrations/)。从插件入口导出,以便 getMigrations() 返回它们。
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.tsIDatabaseMigration
实现 getName()、up()、down() 与 getSource()。getSource() 告诉核心该变更属于哪个数据源(通常 plugin: "default")。
TSXmigration
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}在 IPlugin 上注册
在 backend/src/index.ts 的默认导出插件类中,由 getMigrations() 返回迁移类。需要时用 onMigrate() 做迁移后钩子。
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}执行顺序
- 01
安装 / 升级插件
核心加载 module.js 并读取 getMigrations()。
- 02
应用未执行迁移
每个尚未用过的 getName() 会在 getSource() 上执行 up(queryRunner)。
- 03
onMigrate()
迁移批次完成后的可选插件钩子。
- 04
模块初始化
实体、DI 与 @OnInit 照常继续。
最佳实践
- 迁移名以 metadata.name 为前缀,保证跨插件唯一
- 保持 up/down 成对;down 应撤销同一变更
- 本地/开发优先用 IF EXISTS / IF NOT EXISTS(或等效检查)以便安全重跑
- 已上线的迁移不要改——新增编号迁移
- 实体定义需与迁移最终 schema 对齐