TypeScriptにおけるBookshelf Pluginの型定義

# はじめに
TypeScriptでBookshelf Pluginの型定義を行う方法です。
そろそろ何か書かないとやばいと思い、Advent Calendarに遅刻して来ました。

## Bookshelf Pluginとは
[Bookshelf](https://bookshelfjs.org/)とはNode.jsにおけるORMのひとつで、そのまんまですが、そのPluginがBookshelf Pluginです。
最近のTypeScriptでの流行は[TypeORM](http://typeorm.io/)と聞いていますが…💦。
DAOである`Model`に対してページネーションなどの機能を拡張することができます。
以下のプラグインは、Bookshelf自体に内蔵しており、必要に応じて追加の依存関係を持つことなくすぐに使用することができます。

- Registry: 循環参照を避けるため、`require`関数や`import`文を使用せずに文字列で他の`Model`を参照するプラグイン。リレーションを多く持つモデルなどで使用する。
- Virtuals: モデルにDB上には存在しない仮想のカラムを持たせるプラグイン。
- Visibility: JSONにシリアライズするときに一部のみ見せる・見せないを実現するプラグイン。
- Pagination: `Model`に`fetchPage`メソッドを追加し、`LIMIT`や`OFFSET`指定、全体の列数を自動でしてくれるプラグイン。これが使いたかった。
- Case Converter: DBのカラム名を`snake_case`から`camelCase`に変換したり、その逆をしたりするプラグイン。これは型定義が要らない。
- Processor: `set`メソッドをフックして、挙動を変えることができるプラグイン。これ使ったらなんでもありな気が…💦💦💦。

> [Bookshelf Plugins](https://bookshelfjs.org/#plugins)より

## Pluginの型定義
Bookshelf自体には型定義`@types/bookshelf`が存在するのですが、ライブラリに内蔵しているプラグインは、その型定義には含まれていません。
また、ライブラリ外の[Community Plugins](https://bookshelfjs.org/#community-plugins)はもちろん、自前で型定義をする必要があります。

今回は、ページネーションするプラグインである`pagination`が使いたかったため、この型定義を作成しました。
参考にしたのは、[API Reference](https://bookshelfjs.org/api.html#Model-instance-fetchPage)よりも、実装である[pagination.js](https://bookshelfjs.org/lib_plugins_pagination.js.html)になります。
BookshelfのAPI Referenceは、英語で書かれているため、自分には実装の方がまだ理解できました💦。

```typescript:index.d.ts
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](https://www.typescriptlang.org/docs/handbook/classes.html#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でも探してみましたが、ぱっと見内容が良さそうなのは、
[![image.png](https://qiita-image-store.s3.amazonaws.com/0/78693/b5b5d3d1-7b6f-c8c6-9cc6-f87242fb2253.png) TypeScript実践プログラミング](https://amzn.to/2Aa511S)
ぐらいでした。
言語の進化に対して出版年がかなり古く、`async/await`まだない時の本ですので、皆さん一筆執られたりされませんか。

この記事で示したコードはWTFPL v2としておりますので、責任はとれませんが、ご自由に使っていただいて大丈夫です。
じつはGitHubにも、妙なPRと共に上がっていると思います。
Bookshelf Pluginの型定義はどういう上げ方をしたらいいんでしょうか、ご存知の方教えてください…💦💦💦。