kt-tech.blog

画像

【トラブルシューティング】Prisma v7 の engineType="client" でローカル/Workers 環境を両立させる

Share
💡
Prisma v7 でデフォルトになった engineType="client" により new PrismaClient() がそのままでは動かなくなります。ローカル開発(PostgreSQL 直接接続)と本番(Cloudflare Workers + Prisma Postgres)を 1 つのコードベースで両立させる方法を解説します。

はじめに

Prisma v7 では エンジンタイプのデフォルトが "client" に変更 されました。このエンジンタイプでは PrismaClient のコンストラクタに accelerateUrladapter のいずれかを渡す必要があり、従来の new PrismaClient() だけではエラーになります。

さらに厄介なのが、ローカル開発用のアダプター(@prisma/adapter-pg)を静的に import すると、Cloudflare Workers 向けのビルドが壊れるという問題です。

この記事でわかること

  • Prisma v7 の engineType="client" が引き起こすエラーと原因

  • DATABASE_URL の形式で接続方法をランタイム分岐するパターン

  • eval(require()) でバンドラーの静的解析を回避する手法

  • seed.ts への適用方法

対象読者

  • Prisma v7 にアップグレードしてエラーに遭遇した方

  • Cloudflare Workers とローカル開発を両立させたい方

前提条件

  • Prisma v7+

  • @prisma/adapter-pg パッケージ

  • Cloudflare Workers (OpenNext) でのデプロイ環境

1. エラーの内容

Prisma v7 にアップグレード後、以下のエラーが発生します。

PrismaClientConstructorValidationError:
Using engine type "client" requires either "adapter" or "accelerateUrl"
to be provided to PrismaClient constructor.

これは以下の問題を連鎖的に引き起こします。

  • ローカル DB 接続不可(accelerateUrl は Prisma Postgres 用)

  • seed.ts の失敗(独自の PrismaClient インスタンス)

  • Workers ビルドエラー(@prisma/adapter-pg の静的 import)

2. ランタイム分岐パターン

DATABASE_URL のプロトコルで使用する初期化方法を切り替えます。

// src/lib/prisma.ts
import { PrismaClient } from "@prisma/client";

function createPrismaClient(): PrismaClient {
  const databaseUrl = process.env.DATABASE_URL;
  if (!databaseUrl) {
    throw new Error("DATABASE_URL environment variable is not set");
  }

  // Workers: Prisma Postgres 経由(HTTP)
  if (databaseUrl.startsWith("prisma+postgres://")) {
    return new PrismaClient({ accelerateUrl: databaseUrl });
  }

  // ローカル: @prisma/adapter-pg で TCP 直接接続
  const moduleName = "@prisma/adapter-pg";
  // eslint-disable-next-line no-eval
  const { PrismaPg } = eval(`require("${moduleName}")`);
  const adapter = new PrismaPg({ connectionString: databaseUrl });
  return new PrismaClient({ adapter });
}
💡
prisma+postgres:// → Prisma Postgres(HTTP 経由)、postgresql:// → ローカル PostgreSQL(TCP 直接接続)。URL の形式だけで切り替えるのが最もシンプルです。

3. なぜ eval(require()) なのか

通常の importrequire() では esbuild が静的解析でモジュールをバンドルに含めようとします。

import { PrismaPg } from "@prisma/adapter-pg"
  ↓ esbuild が @prisma/adapter-pg を解析
  ↓ pg パッケージに依存
  ↓ pg が pg-cloudflare を require
  ↓ Workers 向けビルドで pg-cloudflare が解決できずエラー

try-catch で囲んだ require() でも esbuild は解析を行います。eval() で文字列として require を呼ぶことで、esbuild の静的解析を完全に回避できます。

💡
Workers 環境では DATABASE_URLprisma+postgres:// なので、eval(require()) のコードパスには到達しません。ランタイムで問題が起きることはありません。

4. seed.ts への適用

seed.ts も独自の PrismaClient を持つため、同様のパターンを適用します。

// prisma/seed.ts
import { PrismaClient } from "@prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";

const databaseUrl = process.env.DATABASE_URL!;
const prisma = databaseUrl.startsWith("prisma+postgres://")
  ? new PrismaClient({ accelerateUrl: databaseUrl })
  : new PrismaClient({
      adapter: new PrismaPg({ connectionString: databaseUrl }),
    });
💡
seed.tsbun で直接実行されるため、eval(require()) は不要です。esbuild を経由しないので、通常の import で問題ありません。

5. ローカル DB の初期化手順

# DB リセット(pgvector 拡張の再作成が必要)
bunx prisma db push --force-reset

# pgvector 拡張を再作成
PGPASSWORD=postgres psql -h localhost -p 5434 -U postgres -d anoni \
  -c 'CREATE EXTENSION IF NOT EXISTS vector;'

# スキーマ反映 + シード
bunx prisma db push --accept-data-loss
bunx prisma db seed

まとめ

  • Prisma v7 の engineType="client" は breaking change。accelerateUrladapter が必須に

  • DATABASE_URL のプロトコルでランタイム分岐するのが最もシンプル

  • eval(require()) はハックだが、Workers ビルドとローカル開発を両立させる実用的な手法

  • seed.ts は bun 直接実行なので通常の import で OK

  • ローカル DB リセット時は pgvector 拡張の再作成を忘れずに