【Drizzle】queryに__typeを埋め込んで型強力にする

こんにちは、フリーランスエンジニアの太田雅昭です

以前、DrizzleのViewで__typeを埋め込んで、型を強化する話を書きました。

今回は、Viewを使わずqueryでやってしまおうというお話です。

tableを用意する

下記のテーブルを用います。

export const usersTable = pgTable('users', {
  id: text().notNull(),
  handle: text().unique(),
  email: text(),
}

ユーティリティ関数を用意する

下記のようなユーティリティ関数を作ります。

export function mkType<T extends string>(type: T) {
  return { __type: sql<T>`${sql.raw(`'${type}'`)}`.as('__type') }
}

これは、フィールドに__typeとして固定値を入れるためのものです。詳細は前回の記事を参照ください。

queryを作る

queryを作ります。

import { SQL } from "drizzle-orm";

// queryで受け取る引数
export type CustomQueryParams = {
  where?: SQL<unknown>
  orderBy?: SQL<unknown>
  limit?: number
  offset?: number
}

// 自身参照用
export const userAsSelfQuery = (params?: CustomQueryParams) => db()
  .query
  .usersTable
  .findOne({
    extras: { ...mkType('user_self') },
    with: {
      posts: true,
    },
    ...params,
  });

// 公開用
export const usersAsPublicQuery = (params?: CustomQueryParams) => db()
  .query
  .usersTable
  .findMany({
    extras: { ...mkType('users_public') },
    columns: { email: false, }, // 機密情報を除く
    with: {
      posts: true,
    },
    ...params,
  });

型を作ります。type指定しているため、クライアントから読み込んでも安全です。

// type指定でimportすることで、サーバークライアント間のエラーを防ぐ
import type {userAsSelfQuery, usersAsPublicQuery} from 'queries';

export type UserAsSelf = Awaited<ReturnType<typeof userAsSelfQuery>>
export type UserAsPublic = Awaited<ReturnType<typeof usersAsPublicQuery>>[number]

使ってみる

下記のようにして使います。

function showMe(me: UserAsSelf) {
  console.log(me);
}

function showAsPublic(user: UserAsPublic){
  console.log(user);
}

async function test() {
  const me = await userAsSelfQuery();
  const usersPublic = await usersAsPublic();

  showMe(me);
  showMe(usersPublic[0]); // 型エラーになる

  showPublic(me); // 型エラーになる
  showPublic(usersPublic[0]);
}

__typeに固定値が入っているため、予期しないところでの使用時に型エラーが出るようになります。