0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Prisma Clientは「生成物」だと理解したら、よくある2大エラーが一気に�(に)落ちた

0
Posted at

Prismaを使っていると、一度は出くわすエラーがあります。

  • Module '"@prisma/client"' has no exported member 'Review'
  • 'reviews' does not exist in type 'RestaurantSelect'
  • @prisma/client did not initialize yet. Please run "prisma generate"...

僕も例に漏れず両方ハマったんですが、原因を追っていくうちに「これ、結局ぜんぶ同じことを言ってるな」と気づきました。本記事では、別々に見えるこの2系統のエラーを1つの考え方でまとめて片付けます。

検証環境:Next 13(app router)/ Prisma v6 系 / PostgreSQL / TypeScript

結論(TL;DR)

先に結論だけ。困ってる人はここだけ見れば直ると思います。

症状 何が起きてる? 対処
型(Review など)が import できない / select に書いたプロパティが「存在しない」と言われる schemaを変えたのに Client を作り直していない npx prisma db pushnpx prisma generate → サーバー再起動
did not initialize yet. Please run "prisma generate" Clientの出力先と import 元がズレている 生成された場所から import する

で、この2つに共通している大前提がこれです。

Prisma Client は「生成されるコード」。
import してるものと、実際に generate されたものがズレると壊れる。

これさえ腹落ちすれば、上のエラーは全部「ああ、生成がズレてるのね」で説明がつきます。

前提:Prisma Clientは手書きのライブラリじゃない

意外と最初にここでつまずきます。@prisma/client って npm で入れるから普通のライブラリの感覚になりがちなんですが、実体はこうです。

schema.prisma ──(prisma generate)──▶ @prisma/client(生成された型とコード)──▶ あなたの import
  • schema.prisma設計図
  • @prisma/client の中身は、その設計図から 毎回吐き出される成果物

なので「schemaを直しただけ」では、型もランタイムも一切更新されません。generate を走らせて初めて反映される。ここを押さえておくと、以降が全部つながります。

ケース1:型が無い / select で「そんなプロパティ無い」と怒られる

最初にハマったのがこれでした。Restaurantreviews リレーションを足して、いざ取得しようとしたら…

import { PrismaClient, Cuisine, Location, PRICE, Review } from "@prisma/client";
//                                                    ^^^^^^
// Module '"@prisma/client"' has no exported member 'Review'.ts(2305)

const prisma = new PrismaClient();

const fetchRestaurants = async () => {
  const restaurants = await prisma.restaurant.findMany({
    select: {
      id: true,
      name: true,
      reviews: true,
      // ^^^^^ 'reviews' does not exist in type 'RestaurantSelect'.ts(2322)
    },
  });
  return restaurants;
};

Review 型が import できない。selectreviews を書くと「そんなプロパティは無い」と言われる。でも locationcuisine は通る。…謎ですよね。

原因

謎でもなんでもなくて、schemaに Review モデルや reviews リレーションを足したあと、Clientを作り直していないだけでした。location / cuisine が通るのは、それらは前回の generate 時点ですでに存在してたから。後から足した Review だけが生成物に入っていない、という状態です。

直し方

# DBにスキーマを反映(同時に generate も走る)
npx prisma db push

# 念のため Client を再生成
npx prisma generate

そのあと 開発サーバーと TS Server を再起動します。VS Codeなら Cmd/Ctrl + Shift + P →「TypeScript: Restart TS Server」が地味に効きます。エディタが古い型をキャッシュしてるだけ、ってパターンも多いので。

db pushmigrate dev はどっちでもいいの?という疑問が出ると思いますが、ざっくり プロトタイピング中は db push、マイグレーション履歴を残したい本番運用は migrate dev と覚えておけばOKです。今回は型を反映したいだけなので db push で十分。

「node_modules消したら直った」の正体

ちなみに、調べてると「node_modules を消して npm install し直したら直った」という解決報告をよく見ます。あれ、消したのが効いたんじゃなくて、npm install のついでに prisma generate(postinstall)が走って Client が作り直されたから直ってるだけです。なので毎回 node_modules を消す必要はなくて、npx prisma generate 一発で十分。地味に時間の無駄なので覚えておくと幸せになれます。

おまけ:reviews: [ [Object] ] はバグじゃない

取得した結果を console.log して reviews: [ [Object] ] と出ると「データ壊れてる!?」って焦りますが、これはバグじゃないですconsole.log がネストの深いオブジェクトを省略表示してるだけ。中身を見たいときは深さ指定するか、JSONで吐けばちゃんと見えます。

console.dir(restaurants, { depth: null });
// もしくは
console.log(JSON.stringify(restaurants, null, 2));

ついでに:そのschema、リレーション定義ちょっと変かも

これは本筋じゃないんですが、ハマってたときのschemaがこうなってました。

model Restaurant {
  // ...
  review_id Int      @unique   // ← これ要らない
  reviews   Review[]
}

reviews Review[] の 1対多リレーションは、子側(Review.restaurant_id)で定義すれば成立します。親の Restaurant 側に review_id @unique を持たせると「レストラン1件にレビュー1件まで」みたいな意図に見えて混乱の元なので、消してOKです。エラーの直接原因ではないですが、こういう小さいズレが後で効いてくるので一緒に直しておくのがおすすめ。

ケース2:did not initialize yet と言われる

別プロジェクトで今度はこれ。

Error: @prisma/client did not initialize yet.
Please run "prisma generate" and try to import it again.

しかも npx prisma generate はちゃんと成功してる。ログにもこう出てる。

✔ Generated Prisma Client (v6.6.0) to .\generated\prisma in 102ms

「generateできてるのに initialize されてないってどういうこと?」と小一時間悩みました。

原因

ログをよく見ると答えが書いてあります。to .\generated\prisma。つまり Clientの出力先が ./generated/prisma にカスタマイズされてるんです。schemaにこういう指定があるはず。

generator client {
  provider = "prisma-client-js"
  output   = "../generated/prisma"   // ← これ
}

なのに、コード側ではデフォルトの場所から import してる。

const { PrismaClient } = require('@prisma/client'); // ← ここには生成物が無い

要するに「作った場所と取りに行く場所が違う」だけ。ケース1と根っこは同じで、importgenerate の不一致です。

直し方

生成された場所から import すればOK。

// Before
const { PrismaClient } = require('@prisma/client');

// After
const { PrismaClient } = require('./generated/prisma/client');

それでもパス解決でコケる環境なら、絶対パス寄りに組み立てると安定します。

const path = require('path');
const { PrismaClient } = require(path.join('.', 'generated', 'prisma', 'client.js'));

setTimeout で待つのはやめたほうがいい

この件、ネットには「new PrismaClient()setTimeout で100msくらい遅らせれば直る」という回答も転がってます。これは踏まないほうがいいです。

// アンチパターン:たまたま直って見えるだけ
setTimeout(() => {
  const prisma = new PrismaClient();
}, 100);

generate はビルド時の処理、new PrismaClient() は実行時の処理で、そもそも競合する非同期処理じゃありません。今回の原因は純粋に import パス違いなので、待ち時間を入れても本質は何も解決してない。環境やマシンの速度が変われば普通に再発します。「なんか直った」で済ませず、import先を直すのが正解です。

まとめ:エラーが出たら、この順で疑う

最後にフローチャートにしておきます。Prisma系のエラーは、だいたいこの流れで切り分けると早いです。

結局のところ、Prismaのこの手のエラーは

「schemaを変えたら generate」「import先は生成先と合わせる」

この2つを守っていれば、ほとんど踏みません。Clientは手書きのライブラリじゃなくて生成物——この一点を意識するだけで、エラーメッセージの見え方がだいぶ変わると思います。

同じところでハマってる人の助けになれば。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?