Migration

Migration là lớp cơ sở cho migration dữ liệu của NocoBase, dùng để xử lý thay đổi cấu trúc cơ sở dữ liệu và migration dữ liệu khi nâng cấp Plugin. Import từ @nocobase/server.

import { Migration } from '@nocobase/server';

export default class extends Migration {
  on = 'afterLoad';
  appVersion = '<1.0.0';

  async up() {
    // Logic nâng cấp
  }
}

Thuộc tính của lớp

on

on: 'beforeLoad' | 'afterSync' | 'afterLoad';

Kiểm soát thời điểm migration thực thi trong quy trình upgrade. Mặc định 'afterLoad'.

Giá trịThời điểm thực thiTình huống áp dụng
'beforeLoad'Trước khi Plugin được loadThao tác DDL ở tầng thấp (ví dụ thêm cột, thêm ràng buộc), tại thời điểm này chưa thể dùng Repository API
'afterSync'Sau db.sync(), trước khi Plugin upgradeMigration dữ liệu cần cấu trúc bảng mới nhưng không phụ thuộc vào logic Plugin
'afterLoad'Sau khi tất cả Plugin đã được loadGiá trị mặc định, hầu hết migration dùng giá trị này. Có thể dùng đầy đủ Repository API

appVersion

appVersion: string;

Chuỗi semver range, quyết định migration được thực thi trên các phiên bản ứng dụng nào. Framework dùng semver.satisfies() để kiểm tra: chỉ khi phiên bản ứng dụng hiện tại thỏa mãn range này thì migration mới chạy.

// Chỉ chạy khi nâng cấp từ phiên bản thấp hơn 1.0.0
appVersion = '<1.0.0';

// Chỉ chạy khi nâng cấp từ phiên bản thấp hơn 0.21.0-alpha.13
appVersion = '<0.21.0-alpha.13';

// Để trống thì chạy mỗi lần upgrade
appVersion = '';

Thuộc tính của instance

app

get app(): Application

Instance Application của NocoBase. Qua đây bạn có thể truy cập các module của ứng dụng:

async up() {
  // Lấy phiên bản ứng dụng
  const version = this.app.version;

  // Lấy logger
  this.app.log.info('Migration started');
}

db

get db(): Database

Instance Database của NocoBase, có thể dùng để lấy Repository, thực thi truy vấn, v.v.:

async up() {
  const repo = this.db.getRepository('users');
  await repo.update({
    filter: { status: 'inactive' },
    values: { status: 'disabled' },
  });
}

plugin

get plugin(): Plugin

Instance Plugin hiện tại. Chỉ khả dụng trong migration cấp Plugin (trong core migration là undefined).

async up() {
  const pluginName = this.plugin.name;
}

sequelize

get sequelize(): Sequelize

Instance Sequelize, có thể trực tiếp thực thi SQL thô:

async up() {
  await this.sequelize.query(`UPDATE users SET status = 'active' WHERE status IS NULL`);
}

queryInterface

get queryInterface(): QueryInterface

Sequelize QueryInterface, dùng cho thao tác DDL (thêm/xóa cột, thêm ràng buộc, đổi kiểu cột, v.v.):

async up() {
  const { DataTypes } = require('@nocobase/database');

  // Thêm cột
  await this.queryInterface.addColumn('users', 'nickname', {
    type: DataTypes.STRING,
  });

  // Thêm ràng buộc unique
  await this.queryInterface.addConstraint('users', {
    type: 'unique',
    fields: ['email'],
  });
}

pm

get pm(): PluginManager

Trình quản lý Plugin. Qua this.pm.repository bạn có thể truy vấn và sửa metadata Plugin:

async up() {
  const plugins = await this.pm.repository.find();
  for (const plugin of plugins) {
    // Sửa hàng loạt bản ghi Plugin
  }
}

Phương thức của instance

up()

async up(): Promise<void>

Thực thi khi nâng cấp. Lớp con phải override phương thức này, viết logic migration.

down()

async down(): Promise<void>

Thực thi khi rollback. Hầu hết migration để trống. Nếu cần hỗ trợ rollback, viết thao tác ngược ở đây.

Ví dụ đầy đủ

Dùng Repository API để cập nhật dữ liệu (afterLoad)

Tình huống phổ biến nhất - sau khi tất cả Plugin được load xong, dùng Repository API để cập nhật hàng loạt dữ liệu:

import { Migration } from '@nocobase/server';

export default class extends Migration {
  appVersion = '<1.0.0';

  async up() {
    const repo = this.db.getRepository('roles');
    await repo.update({
      filter: {
        $or: [{ allowConfigure: true }, { name: 'root' }],
      },
      values: {
        snippets: ['ui.*', 'pm', 'pm.*'],
        allowConfigure: false,
      },
    });
  }

  async down() {}
}

Dùng QueryInterface để sửa cấu trúc bảng (beforeLoad)

Thực thi DDL ở tầng thấp trước khi Plugin được load - ví dụ thêm cột mới và ràng buộc unique cho bảng:

import { DataTypes } from '@nocobase/database';
import { Migration } from '@nocobase/server';

export default class extends Migration {
  on = 'beforeLoad';
  appVersion = '<0.14.0-alpha.2';

  async up() {
    const tableName = this.pm.collection.getTableNameWithSchema();
    const field = this.pm.collection.getField('packageName');

    // Kiểm tra trước xem field đã tồn tại chưa
    const exists = await field.existsInDb();
    if (exists) return;

    await this.queryInterface.addColumn(tableName, field.columnName(), {
      type: DataTypes.STRING,
    });

    await this.queryInterface.addConstraint(tableName, {
      type: 'unique',
      fields: [field.columnName()],
    });
  }
}

Dùng SQL thô (afterSync)

Sau khi cấu trúc bảng được sync xong, dùng SQL thô để migrate dữ liệu:

import { Migration } from '@nocobase/server';

export default class extends Migration {
  on = 'afterSync';
  appVersion = '<1.0.0-alpha.3';

  async up() {
    const items = await this.pm.repository.find();
    for (const item of items) {
      if (item.name.startsWith('@nocobase/plugin-')) {
        item.set('name', item.name.substring('@nocobase/plugin-'.length));
        await item.save();
      }
    }
  }
}

Tạo file Migration

Tạo qua lệnh CLI:

yarn nocobase create-migration my-migration --pkg @my-project/plugin-hello

Lệnh sẽ tạo file có timestamp trong thư mục src/server/migrations/ của Plugin, theo template:

import { Migration } from '@nocobase/server';

export default class extends Migration {
  on = 'afterLoad';
  appVersion = '<phiên bản hiện tại>';

  async up() {
    // coding
  }
}

Tham số lệnh:

Tham sốMô tả
<name>Tên migration, dùng để tạo tên file
--pkg <pkg>Tên package, quyết định đường dẫn lưu file
--on <on>Thời điểm thực thi, mặc định 'afterLoad'

Liên kết liên quan