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

モデル型の一部分構造に対する操作

Prisma Clientを使用すると、Prismaスキーマのすべてのモデルは専用のTypeScript型に変換されます。例えば、次のUserPostモデルがあると仮定します。

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>