はじめに
現在携わっているプロダクトで TypeORM を使っていて、ここ最近プライベートでも素振りしています。
そんな中、migration を流していると突然 migration name is wrong. Migration class name should have a JavaScript timestamp appended.
と怒られました。
結論を述べると、migration のクラス名に timestamp が載っていない以前に、空のクラス名が渡っていて、そのせいで怒られていました。 では、なぜ空のクラス名が渡るのかと言うと、migration ファイルが格納されているディレクトリ内に、関係のないファイル、正確にはタイムスタンプが追加されていないクラス名が存在すると上記エラーでコケてしまいます。
TL; DR
TypeORM の config で指定した migration のディレクトリには、migration とは関係ないファイルやクラスを置いてはいけない。
内容
TypeORM では、migration 用のクラス名に timestamp を含める必要があります。
1// 公式よりサンプル
2
3import { MigrationInterface, QueryRunner } from "typeorm";
4
5export class PostRefactoringTIMESTAMP implements MigrationInterface {
6 async up(queryRunner: QueryRunner): Promise<void> {}
7
8 async down(queryRunner: QueryRunner): Promise<void> {}
9}
また、TypeORM では migration ファイルが格納されているディレクトリを予め config 等で設定します。
ちなみに私は .js
で管理していて、そのままではないですが、下記のように設定していました。
1// ormconfig.js
2
3module.exports = {
4 type: "postgres",
5 url: DB_URL,
6 entities: ["dist/domain/model/**/*.js"],
7 migrations: ["src/infrastructure/migration/**/*.ts"], // here!!
8 synchronize: false,
9 logging: true,
10};
この設定が不味く、私の場合、/migration
ディレクトリ内に、TypeORM の migration クラスとは関係ない /fixture
(開発・テスト用の DB の mock データ)を置いていて、この fixture では timestamp とは無縁のクラス名にしていました。
1$ tree migration
2
3migration
4├── fixture
5│ ├── index.ts
6│ ├── sex.ts
7│ └── user.ts
8├── row
9│ └── 1800000000001-insert-user.ts
10├── seed
11│ └── 1700000000000-insert-sex.ts
12└── table
13 ├── 1600000000000-create-sex.ts
14 └── 1600000000002-create-user.ts
15
164 directories, 10 files
こんな中、migration:run
をすると、
1migration name is wrong. Migration class name should have a JavaScript timestamp appended.
エラー内容としては、「migration のクラス名にタイムスタンプを加えること」で、内容に従って全てのクラス名を見直してたのですが、同様のエラーでコケてしまいます。 仕方なく TypeORM 本体まで潜って雑に print debug していると、そもそも空のクラス名が渡っていることが分かりました。
1// typeorm/migration/MigrationExecutor.js
2
3/**
4 * Gets all migrations that setup for this connection.
5 */
6MigrationExecutor.prototype.getMigrations = function () {
7 console.log(this.connection.migrations) // 雑 debug !!
8 var migrations = this.connection.migrations.map(function (migration) {
9 var migrationClassName = migration.name || migration.constructor.name
10 var migrationTimestamp = parseInt(migrationClassName.substr(-13), 10)
11 if (!migrationTimestamp || isNaN(migrationTimestamp)) {
12 console.log('error', migration) // 雑 debug !!
13 throw new Error(
14 migrationClassName +
15 " migration name is wrong. Migration class name should have a JavaScript timestamp appended."
16 )
17 }
18 .
19 .
20 .
21}
上記の雑 debug で typeorm migration:run
を実行すると、
1query: SELECT * FROM "information_schema"."tables" WHERE "table_schema" = current_schema() AND "table_name" = 'migrations'
2query: SELECT * FROM "migrations" "migrations" ORDER BY "id" DESC
3[
4 {},
5 {},
6 {},
7 InsertUser1800000000001 {},
8 InsertSex1700000000000 {},
9 CreateSex1600000000000 {},
10 CreateUser1600000000002 {}
11]
12error {}
13Error during migration run:
14Error: migration name is wrong. Migration class name should have a JavaScript timestamp appended.
15 at /app/packages/graphql/src/migration/MigrationExecutor.ts:411:20
そこで、migration に指定するディレクトリ内には、migration とは関係ない timestamp を含んでいないクラスを置いてはいけないと至り、下記のように /fixture
を除いた migration クラスのみが置かれたディレクトリを指定するように config を修正。実行すると無事 migration されることを確認しました。
1// ormconfig.js
2
3module.exports = {
4 type: "postgres",
5 url: DB_URL,
6 entities: ["dist/domain/model/**/*.js"],
7 migrations: [
8 "src/infrastructure/migration/table/**/*.ts",
9 "src/infrastructure/migration/seed/**/*.ts",
10 "src/infrastructure/migration/row/**/*.ts",
11 synchronize: false,
12 logging: true,
13}
おわりに
大前提として migration とは関係ないクラスがあってもよしなに選別してくれると勘違いした私が大問題なのですが、もう少しエラー内容が親切だと嬉しいなと思った一幕でした 💦