okayurisottoによるMisskeyフォークです。13.14.2
をベースにしています。
バックエンドでAPIリクエストのバリデーションに使用していたライブラリを変更するとともに、バリデーション定義を充実させています。これにより、
- より安全なバリデーションができるようになりました
- バックエンドが返すOpenAPI Specがvalidなものになりました
- より円滑に開発できるようになりました
ただしまだWIPな箇所があります。
上記の変更点と関連して、OpenAPI Specがvalidになったことで、そこから生成した型定義をmisskey-jsで使うようにしました。これにより、
- より円滑に開発できるようになりました
- misskey-jsを使ったサードパーティクライアント等の開発についてもより円滑に行えるようになりました
バックエンドでデータベースのORMとして使用していたライブラリを変更するとともに、データベースへの問い合わせを最適化しています。これにより、
- 一部の状況においてパフォーマンスがよくなっているはずです
- より円滑に開発できるようになりました
ただしまだ多くの箇所がWIPです。
これまでAPIコンソールではリクエスト用のダミーJSONを生成し、それを<textarea>
のデフォルト値としていましたが、これを廃止し、代わりにOpenAPI Spec(JSON Schema)を<textarea>
のplaceholder
としました。これにより、
- 妥当でないダミーJSONが生成されてしまう問題を回避しました
- JSON Schemaを読むことで利用者がより適切なJSONを構築できるようにしました
ノート編集中にハッシュタグを入力しようとすると候補が表示されますが、この候補が使用状況に応じて適切に並び替えられていない問題を修正しました。これにより、
- あまり使われていないようなハッシュタグが候補の上位に表示されてしまうことが起きなくなりました
- よく使われているハッシュタグが候補の上位に表示されることで利便性が向上します
既存の公開ユーザーリストから新規にユーザーリストを作成する機能を調整し、元となる公開ユーザーリストに自身をブロックしているユーザーがいた場合にも、ユーザーリストの新規作成自体はエラーにならないようにしました。このとき、作成されるユーザーリストに自身をブロックしているユーザーは含まれません。
- 🚧 型関連
- ✅ TSConfig basesを使うようにする
- 🚧 TSConfigをより厳しくする
- 🚧 型エラーをゼロにする
- 🚧 バリデーション関連
- ✅ AjvからZodへ移行する
- ✅ Zodを使うabstractな
Endpoint
クラスを新しく作る - ✅ 実際のエンドポイントを新しく作ったエンドポイントに置き換える
- ✅ Ajvへの依存をなくす
- ✅ validなOpenAPI Specを配信するようにする
- 🚧 すべての危険な箇所で適切にバリデーションする
- 🚧 データベース関連
- ✅ Prismaをセットアップする
- 🚧 TypeORMをPrismaで置き換えていく - ✅ 基本的な問い合わせ部分をTypeORMからPrismaに置き換える - 😇 Chart部分をTypeORMからPrismaで置き換える
- 🚧 不要な存在確認クエリを消し、エラーハンドリングをきちんとするようにする
- 🚧 トランザクションをきちんとするようにする
- 🚧 適切に
JOIN
して取得し、あとから追加では取得しないようにする- 🚧 トレンド算出のためのクエリを最適化する
- ❓ ハッシュタグの使用履歴を取るようにする
- 🚧 非正規化されている箇所を
schema.prisma
で@ignore
する - ❓
@unique
忘れや@relation
忘れやJson
型やSTIやEAVなどをなくす - ✅ キャッシュによるごまかしをやめる
- 🚧 トレンド算出のためのクエリを最適化する
- ❓
Endpoint
クラスのoverride
方法を工夫する- ❓
meta
やparamDef
をoverride
で定義するようにする
- ❓
- ❓ Conditionalなロールに該当するユーザーの一覧を取得できるようにする
- ❓ 条件をデータベースへのクエリとして表現する
- 🚧 壊してしまったテストをなんとかする
- ❓ ベンチマークを取れるようにする
- ❓ 積極的なジョブキューの活用
- 🚧
@bindThis
をやめる - ❓ タイムライン構築にRedisの集合演算を活用する
- ✅ OpenAPI Specから型定義を自動生成するようにする
- 🚧 型関連
- 🚧 TSConfig basesを使うようにする
- 🚧 TSConfigをより厳しくする
- 🚧 型エラーをゼロにする
- 🚧 バンドラを使うようにする
- 🚧 型関連
- 🚧 TSConfig basesを使うようにする
- 🚧 TSConfigをより厳しくする
- 🚧 型エラーをゼロにする
- 🚧 ぼかし効果に関する設定の初期値をOFFに変更する
リファクタリングを優先しているため新しく機能を実装する予定はありません。
backendでのAPIリクエストのバリデーションではこれまでAjvを使ってきていました。AjvはJSON Schemaと同じ構造をしたオブジェクトを元にデータをバリデーションします。バリデーション用に定義されたそのオブジェクトはOpenAPI Specの生成でも使われていました。
しかしながらこれにはいくつかの問題がありました。まず、Ajvはバリデーションに通った値に対して十分な型定義を提供しません。ゆえにMisskeyでは、バリデーション用定義から型定義を作り出す型が作られ使われていました。しかしこれは型レベルプログラミングの代物であり、リファクタリングのコストは非常に高いものです。ゆえに長らく放置されていました。そしてこれらの状況から、バリデーション用定義がJSON Schemaとしてinvalidである問題が見過ごされたり、invalidなバリデーション定義によってバリデーション結果がany
として型アサーションされてしまう問題が起きたり、invalidなバリデーション定義を使ったOpenAPI Specもまたinvalidになってしまう問題が起きたりしていました。
このフォークではこの問題を解決するため、よりTypeScriptファーストなバリデーションライブラリであるZodへ完全に移行しました。Zodはバリデーション結果に適切な型を定義しますし、Zod定義から型定義を得るような型も提供されています。Zod定義からOpenAPI Specを生成することも、zod2spec
というパッケージを自作することで可能にしました。そしてOpenAPI SpecからTypeScriptの型定義を生成するツール(openapi-typescript
)を使うことでmisskey-jsの型定義の拡充を行いました。
backendでAPIリクエストをより厳密にバリデーションできるようになり、同時にbackendでよりよい型定義を使えるようになり、frontendの開発でもよりよい型定義を得られるようになりました。
Misskeyのbackendにおける型安全性を脅かしていた存在は、不適切な使われ方をしていたAjvだけではありませんでした。データベースへの問い合わせで使われるORMライブラリのTypeORMもまた不適切に使われていました。このフォークではこの問題に対し、TypeScriptファーストなORMライブラリであるPrismaへ部分的に移行することで解決を図りました。現時点ですでに大部分のデータベースと関連した処理でPrismaの提供するAPIや型定義を使うように置き換えられています。
しかし完全な移行には未だ至っていません。MisskeyのTypeORMの使い方には不可解な点が多く、特に難解なChart
周辺はまだTypeORMに依存したままです。
backendではNestJSを用いたDIが行われており、処理が機能ごとに適切に分割されています。しかしながらその分割による弊害か、データベースへの問い合わせすらも分割されてしまっている箇所が多く見られます。適切にJOIN
していれば1回の問い合わせで済むところが、何回も問い合わせる羽目になってしまっていることがあるのです。これは特にcore/entities/*EntityService.ts
におけるpack()
メソッドで顕著です。
このフォークでは問い合わせを減らすために、pack()
メソッド等でのデータベースへの問い合わせをやめ、引数としてデータを渡すように変更しています。このときに登場するのがEntityMap
というものです。これはJavaScriptにあるMap
のシンプルなラッパーです。
下に示すのはユーザーに対する通報をつかさどるサービスです。pack()
メソッドでは第1引数としてpackしたい通報のIDを、第2引数としてその通報と関連したデータを渡しています。実際の処理ではデータからIDをもとにpack対象を得て、packします。
class AbuseUserReportEntityService {
public async pack(
id: abuse_user_report['id'],
data: {
report: EntityMap<'id', abuse_user_report>;
user: EntityMap<'id', user>;
},
): Promise<z.infer<typeof AbuseUserReportSchema>> {
// 処理
}
}
このようなやり方はフォーク以前のMisskeyで使われていた_hint_
引数に影響を受けたものです。_hint_
引数もまたデータベースへの問い合わせを減らすために、事前に取得されたデータを渡すときに使われていました。
実際のpack処理ではomick
パッケージからimportしたpick()
が使われている場面が多くあります。これはTypeScriptにあるユーティリティ型であるPick
に似た働きをするものです。omick
パッケージはこのフォークの開発のために自作されたものです。
このようなユーティリティ関数を作った動機としては、データベースから得た結果をAPIのレスポンスとして整形する際、
return {
id: data.id,
content: data.content,
userId: data.userId,
};
のような書き方が頻出し、いつかtypoしそうだったためです。(すでに実際にserver/api/endpoints/admin/meta.ts
で大文字小文字のtypoが発生しています。)
/packages/omick
を参照していただけるとわかりますが、この自作関数での返り値の型定義ではas
を使用しています。これはそもそものTypeScriptのObject.entries()
やObject.fromEntries()
の型定義があまりよくないためで、望んだものではありません。きちんとテストを書くことで信頼性を担保しているつもりです。