TypeScriptでESNextを使う場合のSequelizeの書き方

TypeScriptでSequelizeを使おうとして、マニュアル(Manual | Sequelize)にある通りに、null assertionを使ってモデルのフィールドを定義してやると、うまく動かなかった。

import { DATEONLY, INTEGER, STRING, Model, Sequelize } from "sequelize";

export const sequelize = new Sequelize("sqlite::memory:");

export class User extends Model {
    id!: number;
    name!: string;
    birth!: string;
}

User.init({
    id: {
        type: INTEGER,
        primaryKey: true,
        autoIncrement: true,
    },
    name: {
        type: STRING,
        allowNull: false,
    },
    birth: {
        type: DATEONLY,
        allowNull: false,
    }
},
    { sequelize }
);

export async function main() {
    await sequelize.sync({ force: true });
    const user = await User.create({
        name: "Albert Einstein",
        birth: "1879-03-14"
    });
    console.log(user.name, user.birth);
}

main();

コンパイラオプションのターゲットがESNextに設定されている場合、上のコードを実行すると undefined undefined と出力される。これは、最新版のTypeScriptが TC39 Stage 3 提案のクラスフィールド に対応しており、ターゲットがESNextになっている場合は、useDefineForClassFieldsコンパイラオプションが有効になって、現行の意味論である「定義」意味論が使われるからだ。(「定義」意味論については、Babelプラグインの順序とallowDeclareFieldsの妙を参照してほしい。)

この問題は、以下のいずれかで解決する:

  • useDefineForClassFieldsオプションをfalseに設定する。
  • null assertion の代わりに declare プロパティ修飾子を使う。
export class User extends Model {
    declare id: number;
    declare name: string;
    declare birth: string;
}

新規のコードを書くときは declare 修飾子を使うようにした方がいいだろう。

参考文献