メインコンテンツへスキップ

モデルタイプの部分構造に対する操作

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型にはモデルのスカラーフィールドのみが含まれており、リレーションは考慮されていません。これは、Prisma Clientクエリではリレーションがデフォルトで含まれないためです。

ただし、場合によってはリレーションを含む型(つまり、includeを使用するAPI呼び出しで取得できる型)を利用できると便利です。同様に、もう1つの便利なシナリオとして、モデルのスカラーフィールドのサブセットのみを含む型(つまり、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名前空間の下でPrisma Clientによって生成および公開される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()

解決策

ネイティブのTypeScriptユーティリティ型AwaitedReturnTypeを使用すると、この問題をスマートに解決できます。

type UsersWithPosts = Awaited<ReturnType<typeof getUsersWithPosts>>
© . All rights reserved.