2022年12月1日

TypeScript 4.9 の `satisfies` は Prisma ワークフローをどのように改善するか

TypeScript の新しい satisfies 演算子により、以前は冗長な型注釈やトリッキーな回避策が必要だった新しいタイプセーフなパターンが可能になります。この記事では、一般的な Prisma 関連のワークフローで役立ついくつかのユースケースについて説明します。

How TypeScript 4.9 `satisfies` Your Prisma Workflows

目次

背景

TypeScript の強みの 1 つは、コンテキストから式の型を推論できることです。たとえば、型注釈なしで変数を宣言すると、その型は代入する値から推論されます。これは、値の正確な型が複雑で、型を明示的に注釈すると多くの重複コードが必要になる場合に特に役立ちます。

ただし、明示的な型注釈が役立つ場合もあります。それらは、コードの意図を他の開発者に伝え、TypeScript エラーをエラーの実際の発生源にできるだけ近づけるのに役立ちます。

サブスクリプションの価格帯を定義し、toFixed メソッドを Number で使用して文字列に変換するコードを考えてみましょう。

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 演算子は役立ちます。

findManycreate のようなメソッドの出力型を推論する

Prisma で satisfies 演算子を使用する最も一般的なユースケースの 1 つは、findUnique のような特定のクエリメソッドの戻り値の型を推論することです。これには、モデルとそのリレーションの選択されたフィールドのみが含まれます。

count メソッドの出力型を推論する

Prisma Client の count メソッドを使用すると、select フィールドを追加して、指定されたフィールドの非 null 値を持つ行をカウントできます。このメソッドの戻り値の型は、指定したフィールドによって異なります。

aggregate メソッドの出力型を推論する

より柔軟な aggregate メソッドの出力形状を取得することもできます。これにより、さまざまなモデルフィールドの平均値、最小値、最大値、およびカウントを取得できます。

groupBy メソッドの出力型を推論する

groupBy メソッドを使用すると、モデルインスタンスのグループに対して集計を実行できます。結果には、グループ化に使用されるフィールドと、集計フィールドの結果が含まれます。 satisfies を使用して出力型を推論する方法を次に示します。

ロスレスなスキーマバリデーターを作成する

スキーマ検証ライブラリ(zodsuperstruct など)は、実行時にユーザー入力をサニタイズするための優れたオプションです。これらのライブラリの一部は、スキーマの静的型を推論することで、重複する型定義を減らすのに役立ちます。ただし、既存の TypeScript 型(Prisma によって生成された入力型など)のスキーマバリデーターを作成したい場合があります。

たとえば、Prisma スキーマファイルに次のような Post 型があるとします。

Prisma は、次の PostCreateInput 型を生成します。

zod でこの型に一致するスキーマを作成しようとすると、スキーマオブジェクトに関する情報の一部が「失われ」ます。

TypeScript 4.9 より前の回避策は、schemaForType 関数(一種の制約付き恒等関数)を作成することでした。 satisfies 演算子を使用すると、スキーマに関する情報を失うことなく、既存の型のスキーマを作成できます。

次に、4 つの一般的なスキーマ検証ライブラリの例を示します。

再利用可能なクエリフィルターのコレクションを定義する

アプリケーションが成長するにつれて、多くのクエリで同じフィルタリングロジックを使用する可能性があります。再利用でき、より複雑なクエリに構成できる一般的なフィルターを定義したい場合があります。

一部の ORM には、これを行うための組み込みの方法があります。たとえば、Ruby on Rails で モデルスコープ を定義したり、Django で カスタムクエリセットメソッド を作成したりできます。

Prisma では、where 条件はオブジェクトリテラルであり、ANDOR、および 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 は、ゼロコストのタイプセーフを提供するように設計されているため、ほとんどの場合、「オプトイン」、コードを型注釈で乱雑にする、またはジェネリック引数を提供することなく、強力な型チェックを自動的に取得できます。

新しい TypeScript 機能(satisfies 演算子など)が、最小限の型ノイズで、より高度なケースでも、より優れたタイプセーフを実現するのにどのように役立つかを楽しみにしています。 Prisma と TypeScript 4.9 をどのように使用しているか、Twitter でお知らせください。

次回の投稿をお見逃しなく!

Prisma ニュースレターにサインアップしてください