TypeScriptの新しいsatisfies
オペレーターは、これまで長い型アノテーションや厄介な回避策が必要だった、新しい型安全なパターンを可能にします。この記事では、一般的なPrisma関連のワークフローでそれが役立ついくつかのユースケースを取り上げます。
目次
- 背景
- `Prisma.validator` なしでPrismaの出力型を推論する
- ロスレスなスキーマバリデーターを作成する
- 再利用可能なクエリフィルターのコレクションを定義する
- 推論された戻り値の型を持つ厳密に型付けされた関数
- まとめ
背景
TypeScriptの強みの一つは、コンテキストから式の型を推論できることです。例えば、型アノテーションなしで変数を宣言すると、その型は代入された値から推論されます。これは、値の正確な型が複雑で、明示的に型をアノテーションすると大量の重複コードが必要になる場合に特に便利です。
しかし、時には明示的な型アノテーションが役立つこともあります。それらは他の開発者にコードの意図を伝えるのに役立ち、TypeScriptのエラーを実際の発生源にできるだけ近くに保ちます。
サブスクリプションの価格帯を定義し、Number
のtoFixed
メソッドを使ってそれらを文字列に変換するコードを考えてみましょう
plans
に明示的な型アノテーションを使用すると、タイプミスをより早く検出できるだけでなく、users
引数の型を推論できます。しかし、別の問題に遭遇する可能性があります
明示的な型アノテーションを使用すると、型が「ワイド化」され、TypeScriptはどのプランがフラット料金で、どのプランがユーザーごとの料金であるかを判別できなくなります。事実上、アプリケーションの型に関する情報が「失われた」ことになります。
私たちが本当に必要としているのは、ある値が広範な/再利用可能な型と互換性があることをアサートしつつ、TypeScriptに、より狭い(より具体的な)型を推論させる方法です。
制約付き同一性関数
TypeScript 4.9より前は、この問題の解決策は「制約付き同一性関数」を使用することでした。これは、引数と型パラメータを受け取り、その2つが互換性があることを保証する、ジェネリックなno-op関数です。
この種の関数の例としては、Prisma.validator
ユーティリティがあります。これは、提供されたジェネリック型で定義された既知のフィールドのみを許可するための追加作業も行います。
残念ながら、この解決策は、コンパイル時にTypeScriptを満足させるためだけに、いくらかのランタイムオーバーヘッドを発生させます。もっと良い方法があるはずです!
`satisfies` の紹介
新しいsatisfies
オペレーターは、ランタイムへの影響なしに同じ利点を提供し、余分なプロパティやスペルミスのプロパティを自動的にチェックします。
TypeScript 4.9では、私たちの価格帯の例がどのように見えるか見てみましょう。
これでタイプミスをソースで直接捕捉できますが、型のワイド化によって情報が「失われる」ことはありません。
この記事の残りの部分では、Prismaアプリケーションでsatisfies
を使用する可能性のある実際の状況をいくつか説明します。
`Prisma.validator` なしでPrismaの出力型を推論する
Prisma Clientは、型安全な結果を得るためにジェネリック関数を使用します。クライアントメソッドから返されるデータの静的型は、クエリで要求した形状と一致します。
これは、Prismaメソッドをインライン引数で直接呼び出す場合に非常にうまく機能します。
しかし、いくつかの落とし穴に遭遇するかもしれません。
- クエリ引数を小さなオブジェクトに分割しようとすると、型情報が「失われ」(ワイド化され)、Prismaが正しい出力型を推論できない可能性があります。
- 特定のクエリの出力を表す型を取得するのは難しい場合があります。
satisfies
オペレーターが役立ちます。
`findMany` や `create` のようなメソッドの出力型を推論する
Prismaでsatisfies
オペレーターを使用する最も一般的なユースケースの1つは、findUnique
のような特定のクエリメソッドの戻り値を推論することです。これには、モデルの選択されたフィールドとそのリレーションのみが含まれます。
`count` メソッドの出力型を推論する
Prisma Clientのcount
メソッドでは、特定のフィールドにnull以外の値を持つ行をカウントするために、select
フィールドを追加できます。このメソッドの戻り値の型は、指定したフィールドによって異なります。
`aggregate` メソッドの出力型を推論する
より柔軟なaggregate
メソッドの出力形状も取得できます。これにより、さまざまなモデルフィールドの平均、最小値、最大値、および数を取得できます。
`groupBy` メソッドの出力型を推論する
groupBy
メソッドを使用すると、モデルインスタンスのグループに対して集計を実行できます。結果には、グループ化に使用されるフィールドと、集計フィールドの結果が含まれます。ここでは、satisfies
を使用して出力型を推論する方法を示します。
ロスレスなスキーマバリデーターを作成する
スキーマ検証ライブラリ(zodやsuperstructなど)は、実行時にユーザー入力をサニタイズするための良い選択肢です。これらのライブラリの中には、スキーマの静的型を推論することで、重複する型定義を減らすのに役立つものもあります。しかし、既存のTypeScript型(Prismaによって生成された入力型など)のスキーマバリデーターを作成したい場合もあります。
例えば、Prismaスキーマファイルに次のようなPost
型がある場合
Prismaは以下のPostCreateInput
型を生成します。
この型に一致するスキーマをzodで作成しようとすると、スキーマオブジェクトに関する情報が「失われます」。
TypeScript 4.9より前の回避策は、schemaForType
関数(制約付き同一性関数の一種)を作成することでした。今ではsatisfies
オペレーターを使用すると、スキーマに関する情報を失うことなく、既存の型に対応するスキーマを作成できます。
ここに、人気のある4つのスキーマ検証ライブラリの例をいくつか示します。
再利用可能なクエリフィルターのコレクションを定義する
アプリケーションが成長するにつれて、多くのクエリで同じフィルタリングロジックを使用するようになるかもしれません。再利用でき、より複雑なクエリに構成できる共通のフィルターを定義したいと思うかもしれません。
いくつかのORMには、これを行うための組み込みの方法があります。例えば、Ruby on Railsではモデルスコープを定義したり、Djangoではカスタムクエリセットメソッドを作成したりできます。
Prismaでは、where
条件はオブジェクトリテラルであり、AND
、OR
、およびNOT
と組み合わせて構成できます。satisfies
オペレーターは、再利用可能なフィルターのコレクションを定義するための便利な方法を提供します。
推論された戻り値の型を持つ厳密に型付けされた関数
関数がReactコンポーネントやRemixローダー関数のような特殊な関数シグネチャに一致することをアサートしたい場合があります。Remixローダーのような場合、TypeScriptにその関数が返す特定の形状を推論させたいとも思います。
TypeScript 4.9より前は、これら両方を一度に達成することは困難でした。satisfies
オペレーターを使用すると、戻り値の型をワイド化することなく、関数が特殊な関数シグネチャに一致することを保証できるようになりました。
Prismaからデータを返すRemixローダーの例を見てみましょう。
ここでは、satisfies
オペレーターが3つのことを行います。
- 私たちの
loader
関数がRemixのLoaderFunction
シグネチャと互換性があることを保証します。 LoaderFunction
シグネチャから関数への引数型を推論するため、手動でアノテーションする必要がありません。- 関数がPrismaから
Post
オブジェクト(関連するcomments
を含む)を返すことを推論します。
まとめ
TypeScriptとPrismaを使用すると、アプリケーションで型安全なデータベースアクセスを簡単に実現できます。PrismaのAPIはゼロコストの型安全性を提供するように設計されているため、ほとんどの場合、「オプトイン」したり、型アノテーションでコードを散らかしたり、ジェネリック引数を提供したりすることなく、自動的に強力な型チェックを得ることができます。
satisfies
オペレーターのような新しいTypeScriptの機能が、より高度なケースでも、最小限の型の煩雑さで、より優れた型安全性を実現するのにどのように役立つかを楽しみにしています。PrismaとTypeScript 4.9をどのように使用しているか、ぜひ私たちのTwitterまでお知らせください。
次の投稿をお見逃しなく!
Prismaニュースレターに登録する