📖

Prisma Client Extensionsを試す(Computed Fields)

2023/02/08に公開

Prisma Client Extensions

PrismaのClient ExtensionsがPreview機能として使えて、PrismaClientを拡張することができるようになりました。

https://www.prisma.io/blog/client-extensions-preview-8t3w27xkrxxn

使い方は多様で、上記にExampleがたくさん乗っています。ということでComputed Fieldsを実際に試してみました。

Computed Fieldsの設定

Client Extensionsを使ってComputed Fields(仮想フィールドと言ったりもする)をPrismaのModelに追加することができます。「データベースの列としては存在しないんだけど、Modelに存在していると便利なフィールド(関数もいける)」が追加できるということです。

本家からの引用 ですが、以下のように定義します。

import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient()
  .$extends({
    result: {
      user: {
        fullName: {
          needs: { firstName: true, lastName: true },
          compute(user) {
            return `${user.firstName} ${user.lastName}`;
          },
        },
      },
    },
  })
  .$extends({
    result: {
      user: {
        displayName: {
          needs: { fullName: true, email: true },
          compute(user) {
            return `${user.fullName} <${user.email}>`;
          },
        },
      },
    },
  });

firstNamelastName がデータベースの列データとして格納されているが、 fullName だったり displayName として、演算した結果を使えるようにする実装です。上記で面白いのは $extends を2回使って、多段で演算している点です。

.$extends({
  result: {
    user: {
      fullName: {
        needs: { firstName: true, lastName: true },
        compute(user) {
          return `${user.firstName} ${user.lastName}`;
        },
      },
    },
  },
})

上記のように1回目は fullNamefirstNamelastName から演算しています。 needs にも firstNamelastName があるのがわかります。

.$extends({
  result: {
    user: {
      displayName: {
        needs: { fullName: true, email: true },
        compute(user) {
          return `${user.fullName} <${user.email}>`;
        },
      },
    },
  },
});

そして、上記のように2回目で、1回目の fullName を使いつつ、さらに email を使って displayName を追加しています。 needs に1回目に定義した fullName と、新たに email が追加されているのが確認できます。

Computed Fieldsを使う

定義が終わったので実際に使ってみます。何がすごいかと言うと、ちゃんとComputed Fieldに対して型補完が効くのです!

上記キャプチャにあるように、 fullNamedisplayName も出てくるのが確認できます。ということで以下のコードを入力すると:

const users = await prisma.user.findMany({ take: 5 });

for (const user of users) {
  console.info(`- ${user.displayName}`);
}

結果は↓のようになります。

- Kamille Berge <Mavis84@yahoo.com>
- Heaven Wisoky <Victoria_Kassulke@yahoo.com>
- Aaliyah Heaney <Gwendolyn61@gmail.com>
- Queenie Kozey <Brayan.Krajcik@gmail.com>
- Eldridge Padberg <Luther.Lowe@hotmail.com>

これはかなり便利に使えそうですね!

おまけ

せっかくなのでもうちょっといじってみます。PrismaのModelは、POJO(Plain Old JavaScript Object)なので、オブジェクトの中身を見ただけでは果たしてUser Modelだったのかどうか、ということが判断できません(instanceofが使えない)。基本的には問題にならないのですが、場合によっては厄介になることがあります(e.g. Modelの種類でロジックを分けたいときなど)。

ということで自前でModelの情報を入れちゃうという使い方もあるかなと思いました。

const prisma = new PrismaClient().$extends({
  result: {
    user: {
      type: {
        needs: {},
        compute() {
          return 'User' as const;
        },
      },
    },
  },
});

ちゃんと型補完も効きます。

switch文とかで使うときにもちゃんと User が補完で出てきます。素敵!

実行すると以下のようにちゃんと User として認識されるのがわかります。

const user = await prisma.user.findFirstOrThrow();
console.log(`The type is ${user.type}`);

結果

The type is User

ということでPrismaのModelの可能性がこれだけでも拡がるのがワクワクしますね。

Discussion