--
--

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Tag:

04
2017

SequelizeをTypeScriptで動かす

CATEGORYJavaScript
JavaScript で一番メジャーらしいORMの Sequelize を TypeScript でも動かしてみたので、その手順とかあれこれ。
なお、Qiitaにも2015年の解説記事があり、自分も参考にさせて頂いたのだけど、バージョンが上がって?微妙に定義が変わったりしてて(TPojoTAttributesとかSequelize.Instanceの型が違うとか)混乱したので、2016年末時点の情報ということで一通り書いとく。

実際に使ってるソースは前回に続きこれ。せっかちな人はGitHubからこの辺(モデル実体, 型定義)持っていっちゃってください~。では以下解説を。

SequelizeのTypeScript対応状況

まずはSequelizeの状況説明。Sequelizeは超メジャーなライブラリだけあって、@types/sequelize にちゃんと型定義ファイルが用意されている。
なので、npmで定義ファイルを持ってくるだけで、簡単にTypeScriptに対応することができる…ライブラリ本体だけは。

Sequelize使ったことある人は分かると思うが、こいつは動的言語の秘儀を極めた、めきょめきょメソッドを生やしたりする系の邪悪なライブラリなので、普通にモデルを実装すると、TypeScriptからはI/Fが全く見えない非常に使い辛いモデルになってしまう(汗
なので、以下は、型定義ファイルを作成して、モデルに実際にはプロパティやメソッドがあるんだよ!ということをコンパイラやIDEに教えてやる作業となる。

TInstance, TAttributes

sequelizeでモデルを定義するのは sequelize.define() 関数だが、その定義はこんな感じになっていた。
define<TInstance, TAttributes>(modelName: string, attributes: DefineAttributes,
options?: DefineOptions<TInstance>): Model<TInstance, TAttributes>;
昔の解説記事や型名、IDEの補完内容から推測するに、TInstancefindById() とかで返されるインスタンスの型、TAttributesbuild() とかに渡すパラメータの型ということのよう。
なので、こんな感じにinterfaceを定義して指定してやる。
export interface AdministratorAttributes {
id?: number;
mailAddress?: string;
password?: string;
role?: string;
createdAt?: Date;
updatedAt?: Date;
deletedAt?: Date;
}

export interface AdministratorInstance extends Sequelize.Instance<AdministratorAttributes>, AdministratorAttributes {
/**
* 渡されたパスワードを現在の値と比較する。
* @param password 比較するパスワード。
* @returns 一致する場合true。
*/
comparePassword(password: string): boolean;
}
TAttributes はただのプロパティ定義だけど、TInstance では Sequelize.Instance という Sequelize のいろんなメソッドが定義されている interface を継承する。
これで、モデルのインスタンスに save() だののメソッドがあることが認識されるようになる。
ここには出てきていないが、hasMany とかの関連で動的にメソッドやプロパティが生成される場合は、そいつらも定義する必要がある()。

また、define() のオプションの instanceMethods でインスタンスメソッドを定義している場合は、↑の comparePassword() のようにその定義も必要になる。
IDEが見るのは型定義の方なので、関数コメントは関数の本体ではなくこっちに書くのがおススメ(両方にあってもいいけど)。

モデルのinterface

上記で Sequelize に渡す型はOKなのだが、define() のオプションの classMethods で静的メソッドを定義している場合、こいつらを参照するためにもう一つ interface が必要になる。
なので、こんな感じに定義する。
export interface AdministratorModel extends Sequelize.Model<AdministratorInstance, AdministratorAttributes> {
/**
* 渡されたパスワードをハッシュ値に変換する。
* @param password 変換するパスワード。
* @param salt 変換に用いるsalt。未指定時は内部で乱数から生成。
* @returns saltとハッシュ値を結合した文字列。
*/
passwordToHash(password: string, salt?: string): string;
}
今度は Sequelize.Model という Sequelize の findAll() みたいな静的メソッドを定義している interface を継承する。
かつ、型はinterface は作っただけじゃダメなので、sequelize.import() してるところで、生成されたモデルに対してこの型を指定してやる必要がある()。

ここまでやってやると、Sequelize のいろんなメソッドが TypeScript で認識されて快適に使えるようになる。長い旅だった…。

まとめ

以上が Sequelize を TypeScript で動かす解説なのだが、いろんな登場人物がいてややこしいと思うので、もう一度まとめる。

種類名称役割
interfaceXxxAttributesモデルのプロパティ定義
XxxInstanceモデルのインスタンス定義
XxxModelモデルそのものの定義
functionXxxモデルの実体

このように、1個の Sequelize のモデルを動かすためには、3つの interface が必要となる。
…うん、この TypeScript とか想定していないライブラリを何とか TypeScript に対応させてみました感が凄いね。。。
なんというか、Sequelize の TypeScript ソースは、昔でいうC言語のヘッダーだけ別に延々と書かされるような、JavaScript のモデル層に TypeScript のI/Fを定義するというような、そんな感じのコードになります。

Sequelize は高機能なので、ここまでお膳立てしてやると、使う側としては非常に快適なんですがね。
ただ、プロパティやメソッドの定義が実体とI/Fで二重になるのは、事故の元というか、なんとも気持ち悪い、、、
TypeScript に対応した typeorm とかもあるようなので(まだ試してない)、そういうのが成長してくれることを望みたい(--;
スポンサーサイト

Tag: JavaScript TypeScript Sequelize

0 Comments

Leave a comment

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。