モデル型の一部分構造に対する操作
Prisma Clientを使用すると、Prismaスキーマのすべてのモデルは専用のTypeScript型に変換されます。例えば、次のUser
とPost
モデルがあると仮定します。
model User {
id Int @id
email String @unique
name String?
posts Post[]
}
model Post {
id Int @id
author User @relation(fields: [userId], references: [id])
title String
published Boolean @default(false)
userId Int
}
このスキーマから生成されたPrisma Clientコードには、User
型のこの表現が含まれています。
export type User = {
id: string
email: string
name: string | null
}
問題:生成されたモデル型のバリエーションの使用
説明
シナリオによっては、生成されたUser
型のバリエーションが必要になる場合があります。例えば、posts
リレーションを持つUser
モデルのインスタンスを期待する関数がある場合や、アプリケーションコード内でUser
モデルのemail
フィールドとname
フィールドのみを渡す型が必要な場合などです。
解決策
解決策として、Prisma Clientのヘルパー型を使用して、生成されたモデル型をカスタマイズできます。
User
型には、モデルのscalarフィールドのみが含まれており、リレーションは考慮されていません。なぜなら、リレーションはPrisma Clientクエリでデフォルトで含まれていないからです。
しかし、時にはリレーションを含む型(つまり、include
を使用するAPI呼び出しから取得する型)が利用できると便利な場合があります。同様に、別の有用なシナリオとして、モデルのscalarフィールドのサブセットのみを含む型(つまり、select
を使用するAPI呼び出しから取得する型)が利用できることも考えられます。
これを実現する方法の1つは、アプリケーションコードでこれらの型を手動で定義することです。
// 1: Define a type that includes the relation to `Post`
type UserWithPosts = {
id: string
email: string
name: string | null
posts: Post[]
}
// 2: Define a type that only contains a subset of the scalar fields
type UserPersonalData = {
email: string
name: string | null
}
これは確かに可能ですが、このアプローチは、型を手動で保守する必要があるため、Prismaスキーマの変更時のメンテナンスの負担を増やします。これに対するよりクリーンな解決策は、Prisma ClientによってPrisma
名前空間の下で生成および公開されるUserGetPayload
型を、validator
と組み合わせて使用することです。
次の例では、Prisma.validator
を使用して2つの型安全なオブジェクトを作成し、次にPrisma.UserGetPayload
ユーティリティ関数を使用して、すべてのユーザーとそれらの投稿を返すために使用できる型を作成します。
import { Prisma } from '@prisma/client'
// 1: Define a type that includes the relation to `Post`
const userWithPosts = Prisma.validator<Prisma.UserDefaultArgs>()({
include: { posts: true },
})
// 2: Define a type that only contains a subset of the scalar fields
const userPersonalData = Prisma.validator<Prisma.UserDefaultArgs>()({
select: { email: true, name: true },
})
// 3: This type will include a user and all their posts
type UserWithPosts = Prisma.UserGetPayload<typeof userWithPosts>
後者のアプローチの主な利点は次のとおりです。
- Prisma Clientの生成された型を活用するため、よりクリーンなアプローチ
- スキーマが変更された場合のメンテナンスの負担軽減と型安全性の向上
問題:関数の戻り型へのアクセス
説明
select
またはinclude
操作をモデルに対して行い、これらのバリアントを関数から返す場合、戻り型へのアクセスが難しい場合があります。例:
// Function definition that returns a partial structure
async function getUsersWithPosts() {
const users = await prisma.user.findMany({ include: { posts: true } })
return users
}
上記のコードスニペットから「投稿付きのユーザー」を表す型を抽出するには、高度なTypeScriptの使用法が必要です。
// Function definition that returns a partial structure
async function getUsersWithPosts() {
const users = await prisma.user.findMany({ include: { posts: true } })
return users
}
// Extract `UsersWithPosts` type with
type ThenArg<T> = T extends PromiseLike<infer U> ? U : T
type UsersWithPosts = ThenArg<ReturnType<typeof getUsersWithPosts>>
// run inside `async` function
const usersWithPosts: UsersWithPosts = await getUsersWithPosts()
解決策
Prisma
名前空間によって公開されているPromiseReturnType
を使用すると、これをよりエレガントに解決できます。
import { Prisma } from '@prisma/client'
type UsersWithPosts = Prisma.PromiseReturnType<typeof getUsersWithPosts>