nestjs typeorm migrations 异步

上一篇 记录了
nestjs nacos+nest-typed-config 实现异步配置加载
呢么接下来就是数据迁移方案了,我们依然采用typeorm migrations 方案.
不过原生的typeorm不支持异步创建datasource所以我们需要改造一下.采用自定义的方式进行迁移.
首先目录结构如下:

image.png

data-source.ts

datasource对象异步创建
需要注意一个报错:[### TypeError: someClass is not a constructor · Issue #5458](https://github.com/typeorm/typeorm/issues/5458)
大概率因为migrations路径加载的内容 包含非迁移文件造成的.

import * as path from 'path';
import { DataSource, DataSourceOptions } from 'typeorm';

import { rootConfig } from '../../src/config/config.module';
import { typedConfigLoadNacos } from '../../src/utils/config.utils';

export const initializeDataSource = async () => {
  const configValue = await typedConfigLoadNacos(rootConfig);
  const typeOrmOptionConfig = configValue.nacosValue.typeormOption;

  const db: DataSourceOptions = {
    type: typeOrmOptionConfig.type,
    host: typeOrmOptionConfig.host,
    port: typeOrmOptionConfig.port,
    username: typeOrmOptionConfig.username,
    password: typeOrmOptionConfig.password,
    database: typeOrmOptionConfig.database, // 移除或保留此项用于连接,不用于生成 SQL
    schema: typeOrmOptionConfig.schema, // 确保指定 schema
    // entities: [__dirname + '/**/*.entity{.ts,.js}'],
    logging: typeOrmOptionConfig.logging,
    migrations: [path.join(__dirname, '../tables/*{.ts,.js}')],
    synchronize: false,
  };
  console.log(JSON.stringify(db));
  const dataSource = new DataSource(db);
  // 确保 DataSource 已经初始化
  return await dataSource
    .initialize()
    .then((value) => {
      return value;
    })
    .catch((error) => {
      console.log(error);
      process.exit(1);
    });
};

run-migration.ts

迁移 run 执行

import { initializeDataSource } from './data-source';

const runMigration = async () => {
  try {
    const dataSource = await initializeDataSource();
    await dataSource.runMigrations();
    console.log('🏅 Migrations executed successfully! 数据迁移成功!');
  } catch (err) {
    console.error('🙋 Error running migrations:', err);
  } finally {
    process.exit(0); // 无论成功还是失败,都退出程序
  }
};

runMigration();

revert-migration.ts

revert 执行

import { initializeDataSource } from './data-source';

const runMigration = async () => {
  try {
    const dataSource = await initializeDataSource();
    await dataSource.undoLastMigration();
    console.log('🎮 Migrations executed successfully! 数据迁移回滚成功!');
  } catch (err) {
    console.error('🙋 Error running migrations:', err);
  } finally {
    process.exit(0); // 无论成功还是失败,都退出程序
  }
};

runMigration();

common.ts

辅助类

// common 通用类

import { TableColumnOptions, TableForeignKeyOptions } from 'typeorm';

/**
 * 默认列
 */
export const defaultColumn = (): TableColumnOptions[] => {
  return [
    {
      name: 'id',
      type: 'varchar',
      length: '50',
      isPrimary: true,
    },
    {
      name: 'enable_at',
      type: 'int',
      default: 1,
      comment: '状态: 1 正常 0 停用',
    },
    {
      name: 'remark',
      type: 'varchar',
      length: '500',
      isNullable: true,
      comment: '状态: 1 正常 0 停用',
    },
    {
      name: 'created_at',
      type: 'timestamp',
      default: 'CURRENT_TIMESTAMP',
      comment: '创建时间',
    },
    {
      name: 'created_id',
      type: 'varchar',
      length: '50',
      isNullable: true,
      comment: '创建人',
    },
    {
      name: 'updated_at',
      type: 'timestamp',
      default: 'CURRENT_TIMESTAMP',
      onUpdate: 'CURRENT_TIMESTAMP',
      comment: '更新时间',
    },
    {
      name: 'updated_id',
      type: 'varchar',
      length: '50',
      isNullable: true,
      comment: '更新时间',
    },
    {
      name: 'deleted_at',
      type: 'timestamp',
      isNullable: true,
      comment: '删除时间',
    },
  ];
};

/**
 * 根据 process.env.NODE_ENV 只在dev环境生成 约束
 * @param array
 */
export const envForeignKeys = (array: TableForeignKeyOptions[]) => {
  console.log('NODE_ENV', process.env.NODE_ENV);
  if (process.env.NODE_ENV !== 'production') {
    return array;
  }
  return [];
};

/**
 * 是否创建外键
 */
export const createForeignKey = process.env.NODE_ENV !== 'production';

script 命令

{
  "script": {
    "typeorm": "typeorm-ts-node-commonjs",
    "m:create": "npm run typeorm migration:create",
    "m:run": "cross-env NODE_ENV=development && ts-node migrations/config/run-migration.ts",
    "m:revert": "cross-env NODE_ENV=development && ts-node migrations/config/revert-migration.ts"
}
}

demo

import { MigrationInterface, QueryRunner, Table } from 'typeorm';

import { createForeignKey, defaultColumn } from '../utils/common';

export class User1732203320222 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.createTable(
      new Table({
        name: 'user',
        comment: '用户表',
        columns: [
          ...defaultColumn(),
          {
            name: 'nickname',
            type: 'varchar',
            length: '50',
            isNullable: true,
            comment: '昵称',
          },
          {
            name: 'account',
            type: 'varchar',
            length: '50',
            isNullable: true,
            comment: '账号',
          },
          {
            name: 'phone_number',
            type: 'varchar',
            length: '20',
            isNullable: true,
            comment: '手机号',
          },
          {
            name: 'password',
            type: 'varchar',
            length: '150',
            isNullable: true,
            comment: '密码',
          },
          {
            name: 'last_time',
            type: 'timestamp',
            isNullable: true,
            comment: '最后登录时间',
          },
          {
            name: 'wx_openid',
            type: 'varchar',
            length: '50',
            isNullable: true,
            comment: '微信授权登录openid',
          },
          {
            name: 'zfb_openid',
            type: 'varchar',
            length: '50',
            isNullable: true,
            comment: '支付宝授权登录openid',
          },
        ],
      }),
      createForeignKey,
    );
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.dropTable('user');
  }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349

推荐阅读更多精彩内容