Help us understand the problem. What is going on with this article?

TypeScriptにおけるBookshelf Pluginの型定義

More than 1 year has passed since last update.

はじめに

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: ModelfetchPageメソッドを追加し、LIMITOFFSET指定、全体の列数を自動でしてくれるプラグイン。これが使いたかった。
  • Case Converter: DBのカラム名をsnake_caseからcamelCaseに変換したり、その逆をしたりするプラグイン。これは型定義が要らない。
  • Processor: setメソッドをフックして、挙動を変えることができるプラグイン。これ使ったらなんでもありな気が…💦💦💦。

Bookshelf Pluginsより

Pluginの型定義

Bookshelf自体には型定義@types/bookshelfが存在するのですが、ライブラリに内蔵しているプラグインは、その型定義には含まれていません。
また、ライブラリ外のCommunity Pluginsはもちろん、自前で型定義をする必要があります。

今回は、ページネーションするプラグインであるpaginationが使いたかったため、この型定義を作成しました。
参考にしたのは、API Referenceよりも、実装であるpagination.jsになります。
BookshelfのAPI Referenceは、英語で書かれているため、自分には実装の方がまだ理解できました💦。

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によると、

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 TypeScript実践プログラミング
ぐらいでした。
言語の進化に対して出版年がかなり古く、async/awaitまだない時の本ですので、皆さん一筆執られたりされませんか。

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

leak4mk0
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away