【Drizzle】画期的?Viewに__typeフィールドを埋め込んでセキュリティを高める

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

DrizzleのView

DirzzleではスキーマレベルでViewを作成することができます。

export const usersTable = pgTable('users', {
  id: text().unique(),
  handle: text().unique().$defaultFn(() => 'user_' + createId()),
  email: text().unique(),
  phone: text().unique(),
  passwordHash: text(),
});

export const usersPublicView = pgView('users_public')
  .as(qb => qb
    .select({
      id: usersTable.id,
      handle: usersTable.handle,
    })
    .from(usersTable));

この例では、usersTableからidとhandleのみのViewを作成しています。便利ですね。

__typeを入れてみる

これに__typeを入れてみます。

__type: sql<'user_public'>`'user_public'`.as('__type'),

これで、Viewから生成される型に__typeフィールドが固定値’user_public’が追加されるようになります。

type UserPublic = typeof usersPublicView.$inferSelect

// type UserPublic = {
//   __type: "user_public";
//   id: string;
//   handle: string | null;
// }

__typeの設定が複雑ですので、関数にします。

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

これを使うと、Viewは以下のようになります。

export const usersPublicView = pgView('users_public')
  .as(qb => qb
    .select({
      ...mkType('user_public'),
      id: usersTable.id,
      handle: usersTable.handle,
    })
    .from(usersTable));

試してみる

試してみました。


type User = typeof usersTable.$inferSelect
type UserPublic = typeof usersPublicView.$inferSelect

const showUser = (user: User) => {
  console.log(user);
}

const showUserPublic = (user: UserPublic) => {
  console.log(user);
}

const user: User = {} as User;
const userPublic: UserPublic = {} as UserPublic;

showUser(user);
showUser(userPublic); // 型エラー
showUserPublic(user); // 型エラー
showUserPublic(userPublic);

うまい具合に、型レベルで漏洩を検知することができるようになりました。意図しない挿入が防止できています。これは使えそうですね。

おまけ: Remedaを入れてみる

RemedaでViewをさらに型安全に書けます。

import * as R from 'remeda';

export const usersPublicView = pgView('users_public')
  .as(qb => qb
    .select({
      ...mkType('user_public'),
      ...R.pick(usersTable, ['id', 'handle']),
    })
    .from(usersTable));

注意: バージョンによっては使えない

Drizzleのバージョンによっては、Viewの生成でエラーになります。2025年8月23日 v0.44.3時点ではまだ解決していないようです。

https://github.com/drizzle-team/drizzle-orm/issues/4731

代替案として、今回はViewを作成せず、下記のSelectの場合の手法を絡めて対応しました。queryでも使えます。将来的にFixされれば、Viewの方がシンプルでいいかと思います。ただqueryでの書きやすさも捨て難いです。