はじめに
TypeScriptでBookshelf Pluginの型定義を行う方法です。
そろそろ何か書かないとやばいと思い、Advent Calendarに遅刻して来ました。
Bookshelf Pluginとは
BookshelfとはNode.jsにおけるORMのひとつで、そのまんまですが、そのPluginがBookshelf Pluginです。
最近のTypeScriptでの流行はTypeORMと聞いていますが…💦。
DAOであるModel
に対してページネーションなどの機能を拡張することができます。
以下のプラグインは、Bookshelf自体に内蔵しており、必要に応じて追加の依存関係を持つことなくすぐに使用することができます。
- Registry: 循環参照を避けるため、
require
関数やimport
文を使用せずに文字列で他のModel
を参照するプラグイン。リレーションを多く持つモデルなどで使用する。 - Virtuals: モデルにDB上には存在しない仮想のカラムを持たせるプラグイン。
- Visibility: JSONにシリアライズするときに一部のみ見せる・見せないを実現するプラグイン。
- Pagination:
Model
にfetchPage
メソッドを追加し、LIMIT
やOFFSET
指定、全体の列数を自動でしてくれるプラグイン。これが使いたかった。 - Case Converter: DBのカラム名を
snake_case
からcamelCase
に変換したり、その逆をしたりするプラグイン。これは型定義が要らない。 - Processor:
set
メソッドをフックして、挙動を変えることができるプラグイン。これ使ったらなんでもありな気が…💦💦💦。
Pluginの型定義
Bookshelf自体には型定義@types/bookshelf
が存在するのですが、ライブラリに内蔵しているプラグインは、その型定義には含まれていません。
また、ライブラリ外のCommunity Pluginsはもちろん、自前で型定義をする必要があります。
今回は、ページネーションするプラグインであるpagination
が使いたかったため、この型定義を作成しました。
参考にしたのは、API Referenceよりも、実装であるpagination.jsになります。
BookshelfのAPI Referenceは、英語で書かれているため、自分には実装の方がまだ理解できました💦。
import * as Bookshelf from 'bookshelf';
import * as BlueBird from 'bluebird';
declare module 'bookshelf' {
interface Model<T extends Model<any>> {
fetchPage(options?: FetchPageSizeOptions): BlueBird<PaginationCollection<T, SizePagination>>;
fetchPage(options?: FetchPageLimitOptions): BlueBird<PaginationCollection<T, LimitPagination>>;
}
interface Collection<T extends Model<any>> {
fetchPage(options?: CollectionFetchPageSizeOptions): BlueBird<PaginationCollection<T, SizePagination>>;
fetchPage(options?: CollectionFetchPageLimitOptions): BlueBird<PaginationCollection<T, LimitPagination>>;
}
interface FetchPageSizeOptions extends FetchOptions {
pageSize?: number;
page?: number;
}
interface FetchPageLimitOptions extends FetchOptions {
limit: number;
offset: number;
}
interface CollectionFetchPageSizeOptions extends CollectionFetchOptions {
pageSize?: number;
page?: number;
}
interface CollectionFetchPageLimitOptions extends CollectionFetchOptions {
limit: number;
offset: number;
}
interface PaginationCollection<T extends Model<any>, U extends Pagination> extends Collection<T> {
pagination: U;
}
interface Pagination {
rowCount: number;
pageCount: number;
}
interface SizePagination extends Pagination {
page: number;
pageSize: number;
}
interface LimitPagination extends Pagination {
offset: number;
limit: number;
}
}
悩んだのはclass Model<T extends Model<any>>
に対して、どのように追加のメソッドを定義するかでしたが、同名のインターフェースとして定義することで、追加できました。
公式リファレンスのUsing a class as an interfaceによると、
Using a class as an interface
As we said in the previous section, a class declaration creates two things: a type representing instances of the class and a constructor function. Because classes create types, you can use them in the same places you would be able to use interfaces.
とあり、勝手訳すると、
インターフェースとしてクラスの使用
前述の通り、クラス宣言は、次の2つの物、クラスのインスタンスとコンストラクター関数を表す型を生成する。クラスがそれらの型を生成することにより、それらをいくつかの個所でインターフェースとして使用することができる。
とあります。
TypeScriptではインターフェースを複数定義した場合、一つのインターフェスにマージされるため、結果としてクラスと同名のインターフェースに追加するメソッドを定義することで、今回のようなケースに対処できた見たいです。
さいごに
TypeScript自体の本って少ないですよね。
エンジニアの皆様方に置かれては、英語がプリインストールされてるので、公式リファレンスを見よということでしょうか…💦💦💦。
天下のAmazonでも探してみましたが、ぱっと見内容が良さそうなのは、
TypeScript実践プログラミング
ぐらいでした。
言語の進化に対して出版年がかなり古く、async/await
まだない時の本ですので、皆さん一筆執られたりされませんか。
この記事で示したコードはWTFPL v2としておりますので、責任はとれませんが、ご自由に使っていただいて大丈夫です。
じつはGitHubにも、妙なPRと共に上がっていると思います。
Bookshelf Pluginの型定義はどういう上げ方をしたらいいんでしょうか、ご存知の方教えてください…💦💦💦。