prisma-next-queries について
prisma-next-queriesは、Prisma Next開発者向けのデータクエリスキルで、ORMと低レベルのクエリインターフェースによる読み書きを扱います。リレーション、絞り込み、集計、トランザクション、結果の扱いを、PostgresとMongo別に説明します。
主な機能
- 01ORMによるデータの読み書きと削除
- 02低レベルのクエリインターフェースの利用
- 03リレーションの読み込みと絞り込み・並べ替え
- 04ページネーション、射影、集計
- 05非同期結果とトランザクションの処理
活用シーン
- 01モデルレコードの検索と絞り込み
- 02レコードと関連データの読み込み
- 03クエリ結果のページネーションや集計
- 04スクリプトでのクエリ実行とデータベースの終了
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ prisma/ prisma-next --skill prisma-next-queriesインストール後、Claude Code で/prisma-next-queriesを入力して読み込まれたか確認します。
同梱ファイルmongo.mdpostgres.md
詳細
Prisma Next — クエリ
データ契約を編集すれば、あとは Prisma が処理します。
契約が生成され、データベースが最新の状態になったら、このスキルではデータに対して行うすべての操作を扱います。データの読み取り、書き込み、リレーションの先行読み込み、集計、そして ORM と低レベルのクエリレーンのどちらを選ぶかについて説明します。
使用する場面
- データの読み取り、書き込み、更新、削除をしたい場合。
- リレーションを含める、または先行読み込みしたい場合。
- ページネーション、並べ替え、フィルタリング、プロジェクションを行いたい場合。
- 操作をトランザクションでラップしたい場合(
db.transaction(...)— Postgres と SQLite)。 - 集計(
count、sum、avgなど)を行いたい場合。 - クエリレーン(ORM と SQL ビルダー/クエリビルダー)について質問がある場合。
- 次の用語が出てくる場合:query、select、where、orderBy、take、skip、include、eager load、first、all、count、aggregate、create、update、delete、upsert、returning、drizzle-style、kysely-style、prisma client。
使用しない場面
- モデルを追加または変更したい場合 →
prisma-next-contract。 db.tsを接続したい、またはミドルウェアを追加したい場合 →prisma-next-runtime。- Supabase のロールにバインドされた db(
asUser/asAnon/asServiceRole、RLS、auth.*の管理者読み取り)経由でクエリを実行する場合 → ロールバインドのインターフェースにはprisma-next-supabaseを使用してください。このスキルの内容は、返されたRoleBoundDbにすべて適用されます。 - クエリの失敗(構造化されたエラーエンベロープ)をデバッグしたい場合 →
prisma-next-debug。
対象を選ぶ
Prisma Next は、src/prisma/db.ts の同じ db 値で、対象ごとに 2 つのクエリレーンを提供します。クエリを書く前に db.ts を読み、対応する対象ガイドを読み込んでください。
db.ts 内のランタイムインポート | 読み込むファイル |
|---|---|
@prisma-next/postgres/runtime | postgres.md — db.orm.<Model> + db.sql.<table> |
@prisma-next/mongo/runtime | mongo.md — db.orm.<root> + db.query.from(...) |
@prisma-next/extension-supabase/runtime | postgres.md — Supabase の RoleBoundDb は Postgres のインターフェースです(db.orm.<Model> + db.sql.<table>)。まず prisma-next-supabase を使ってロールをバインドしてください。 |
どちらの対象も、同じ db 値上で契約と接続を共有します。まず ORM を使い、ORM では表現できない形状の場合に低レベルのレーンを利用してください。レーンの選択は個々のクエリに適用されます。クエリ関数ごとに一方のレーンを選ぶのであり、アプリ全体で選ぶものではありません。
異なる対象の例を混在させないでください。 Postgres では PascalCase のモデルルート(db.orm.User)と db.sql.user を使い、Mongo では小文字の複数形のルート(db.orm.users)と db.query.from('users') を使います。Mongo には db.sql がなく、Postgres には db.query に相当する SQL ビルダーはありません。
名前空間を考慮したアクセサー
契約で複数の名前空間(例:public と auth)を宣言している場合、モデルとテーブルは名前空間座標で指定します。
- ORM:
db.orm.<namespace>.<Model>— 例:db.orm.public.User、db.orm.auth.User - SQL ビルダー:
db.sql.<namespace>.<table>— 例:db.sql.public.users、db.sql.auth.users
単一名前空間の契約では(またはすべてのテーブル名が名前空間間で一意の場合)、フラットな形式の db.orm.User / db.sql.users も引き続き使用できます。同じ名前が複数の名前空間に存在する場合は、名前空間座標を使う必要があります。
使用例については、postgres.md § 名前空間を考慮したアクセサーを参照してください。
結果の消費:await、.toArray()、または for await
早い段階で正しく理解しておくことが重要です。Postgres と Mongo のどちらでも、.all() は AsyncIterableResult<Row> を返します。これは PromiseLike<Row[]> と AsyncIterable<Row> の両方を兼ねています。つまり、3 通りの消費方法がすべて使えます。標準的な方法は最も短いものです。
const users = await db.orm.User.select('id', 'email').all();
// ^? Row[] ← the Thenable resolves to a real array. This is the default idiom.
collect()/toArray() ヘルパーは不要です。await だけで十分です。内部では、await が結果の then(...) を呼び出し、行を配列にバッファーします。より読みやすい場合には、同等の方法が 2 つあります。
// `.toArray()` returns a genuine `Promise<Row[]>`. Reach for it only when
// something needs a real `Promise` and not merely a thenable: a slot typed
// `Promise<Row[]>` (an `AsyncIterableResult` has only `then`, not `catch` /
// `finally`, so it does not satisfy that annotation), or a runtime
// `instanceof Promise` check. Note that `await` and the `Promise.all` /
// `Promise.race` combinators all accept the thenable directly — those are
// NOT reasons to call `.toArray()`. Whenever you are just going to await it
// here, use `await ...all()` and skip `.toArray()`.
const rows: Promise<User[]> = db.orm.User.select('id', 'email').all().toArray();
// Streaming — process rows one at a time without buffering the whole result.
// Use for genuinely large result sets (anything that wouldn't fit comfortably
// in memory) or pipelines where you can start work before all rows arrive.
for await (const user of db.orm.User.select('id', 'email').all()) {
process(user);
}
結果には、コレクションレベルの .first()(Postgres では LIMIT 1 を発行します)に加えて、単一行を取得するショートカットが 2 つあります。
const user = await db.orm.User.where({ id }).all().first();
// ^? Row | null ← buffers, returns the first row or null. Issues no LIMIT.
const required = await db.orm.User.where({ id }).all().firstOrThrow();
// ^? Row ← buffers; throws `RUNTIME.NO_ROWS` if empty.
単一行だけを読み取る場合は、.all().first()(すべての行を取得して残りを破棄します)よりも、コレクションレベルの .first()(Postgres では SQL に LIMIT 1 を追加します)を優先してください。結果レベルのヘルパーは、結果全体がすでに必要で、余分なラウンドトリップを発生させずに先頭の行を取得したい場合に使用します。
結果は一度しか消費できません。 各 AsyncIterableResult インスタンスは、await、.toArray()、for await のいずれかで 1 回だけ消費できます。2 回目に消費しようとすると、RUNTIME.ITERATOR_CONSUMED がスローされます。ほとんどの場合、最初に消費したときに配列を変数に格納し、その変数を再利用すれば解決できます。
// Bad — second await throws RUNTIME.ITERATOR_CONSUMED.
const result = db.orm.User.select('id', 'email').all();
const a = await result;
const b = await result;
// Good — buffer once, reuse the array.
const users = await db.orm.User.select('id', 'email').all();
const a = users;
const b = users;
コードベース内で、.all() をラップする collect(...)/toArray(...) ヘルパーを見かけた場合、それらはすでに不要です。await だけで同じことを追加コストなしに実行できます。周辺のコードに手を加える際に削除してください。
短いスクリプトからクエリを実行する
ユーザーが長時間稼働するサーバーではなく、単発の tsx my-script.ts を実行する場合は、プロセスを正常に終了させるため、最後に await db.close() を呼び出してください。Postgres ではファサードが所有するプールが Node のイベントループを維持し、Mongo ではファサードが所有する MongoClient が同じ役割を果たします。await using を含む全体のパターンについては、prisma-next-runtime § スクリプトとして実行する場合(終了処理) を参照してください。
// src/scripts/seed.ts
import { db } from '../prisma/db';
// Postgres — PascalCase model root from contract
for (const u of users) {
await db.orm.User.create(u);
}
// Mongo — lowercased plural root from contract (e.g. users, not User)
// for (const u of users) {
// await db.orm.users.create(u);
// }
console.log('Seeded.');
await db.close();
よくある落とし穴(ターゲット共通)
- Mongo プロジェクトで Postgres の例を使う(またはその逆)。
db.tsを確認し、適切なターゲットガイド(postgres.mdまたはmongo.md)を読み込んでください。 .all()を配列に変換するためにcollect()/toArray()ヘルパーを書く。.all()はAsyncIterableResult<Row>を返します。これはPromiseLike<Row[]>でもあります。そのため、await collection.all()だけで直接Row[]が得られます。上記の 結果の消費 を参照してください。AsyncIterableResultを 2 回消費する。 各結果は 1 回しか使えません。2 回目に消費するとRUNTIME.ITERATOR_CONSUMEDがスローされます。1 回だけバッファーして変数に格納し、その変数を再利用してください。
ターゲット固有の落とし穴については、各ターゲットのガイドを参照してください。
Prisma Next がまだ対応していないこと
- ジャンクションテーブルを介した N:M の
.include()。 コントラクト IR はthroughジャンクションテーブルを使った多対多リレーションをサポートしており、N:Mリレーションは ORM コレクション上で有効なリレーション名として表示されます。しかし、N:M リレーションに対する.include()は、ジャンクションテーブルを介した2段階の結合を生成しません。クエリプランビルダーは直接結合の列(localColumn/targetColumn)のみを処理し、throughメタデータを無視します。そのため、使用すると誤った結果になるか、エラーが発生します。回避策:ジャンクションテーブルとの明示的な結合を指定し、db.sql.<table>を使って N:M のたどり方を表現してください。 - N:M のネストされたミューテーション。
mutation-executor.tsは、N:M リレーションを介したネストされた作成/リンクに対して、明示的に'N:M nested mutations are not supported yet'をスローします。 - Postgres ファサードの
and/or/notコンビネーター。 現在、コンビネーターは内部パッケージの@prisma-next/sql-orm-clientからインポートされます。現時点での回避策:サンプルアプリと同様に、@prisma-next/sql-orm-clientから直接インポートしてください。@prisma-next/postgres/runtimeから使えるようにしたい場合は、prisma-next-feedbackから機能リクエストを送ってください。 - グループ化された集計に対する
.orderBy(...)/.take(...)(Postgres)。db.orm.<Model>.groupBy(...).aggregate(...)はPromise<Array<Group & Aggregates>>を実体化し、DB レイヤーでの並べ替えや行数制限を提供しません。その結果、「SUM による上位 N グループ」のクエリでは、グループ化された結果全体を対象に、JS 側で並べ替えとスライスを行います。カーディナリティが小さい場合は問題ありませんが、規模が大きくなると負荷が高くなります。回避策:(a)db.sql.<table>に切り替え、集計対象のテーブルに対してGROUP BY+ORDER BY+LIMITを記述する。(b)グループ数が一定範囲内なら、JS 側での並べ替えとスライスを受け入れる。本番環境で問題になっている場合は、prisma-next-feedbackから機能リクエストを送ってください。 - 生 SQL の実行経路。 Prisma Next は、現在ユーザー向けの生 SQL インターフェイス(
db.sql.raw(...)など)を提供していません。回避策:SQL ビルダーでクエリをモデル化してください。ビルダーでまだ表現できない形の場合は、prisma-next-feedbackからその形を説明する機能リクエストを送ってください。ビルダーの機能拡張と生 SQL の実行経路の提供のどちらが適切か、チームが判断できます。 - TypedSQL(
.sqlファイルを型付き呼び出し可能関数にコンパイルする機能)。 未実装です。回避策:SQL ビルダーを使用してください。クエリを繰り返し使う場合は、構築済みのプランを返す関数に切り出し、呼び出し元でdb.runtime().execute(plan)を呼び出してください。.sqlファイルをコンパイルする方法が必要な場合は、prisma-next-feedbackから機能リクエストを送ってください。 EXPLAIN/ クエリプランの検査。 Prisma Next は.explain()メソッドを提供していません。回避策:管理下のpg.Poolをランタイムのpg:バインディング経由で接続し(prisma-next-runtimeを参照)、そこからEXPLAIN ANALYZEを実行してください。プラン検査用の正式なインターフェイスが必要な場合は、prisma-next-feedbackから機能リクエストを送ってください。- 大規模な結果セットのストリーミング。 現時点では
.stream()カーソルはありません。回避策:中規模のデータには.skip(n).take(m)でページネーションしてください。非常に大規模なデータの場合は、ランタイムのpg:バインディングからpg.Clientを取得し、直接ストリーミングしてください。組み込みのストリーミングインターフェイスが必要な場合は、prisma-next-feedbackから機能リクエストを送ってください。 - 複数ステートメントのバッチ処理(Prisma-7 スタイルの
db.$transaction([call1, call2]))。 Prisma Next は各呼び出しを順次実行します。回避策:Postgres では、原子的に扱う必要がある処理をdb.transaction(async (tx) => { ... })でラップしてください。配列を使ったバッチ処理のセマンティクスが必要な場合は、prisma-next-feedbackから機能リクエストを送ってください。 - Mongo ファサードのトランザクション。
@prisma-next/mongo/runtimeはdb.transaction(...)を公開していません。複数ドキュメントの原子性は、まだ Prisma Next の Mongo ファサードではラップされていません。回避策:クライアントのバインディング(mongoClient:オプション)を管理している場合は、MongoDB ドライバーのセッション API を直接使用してください。ファサードに正式なインターフェイスが必要な場合は、prisma-next-feedbackから機能リクエストを送ってください。 - Mongo ORM の集計。
db.orm.<root>には.aggregate(...)/.groupBy(...)がありません。回避策:db.query.from(...).group(...).build()とruntime.execute(plan)で集計を表現してください。 - Mongo ファサードのフィルターヘルパー。 高機能なフィルター(
.in、範囲指定、ブール式の合成)は現在@prisma-next/mongo-query-ast/execution(MongoFieldFilterなど)からインポートします。まだ@prisma-next/mongo/runtimeから再エクスポートされていません。回避策:可能な場合はオブジェクトの等価条件.where({ field: value })を使ってください。内部パッケージからのインポートは必要な場合にのみ行ってください。Linear のTML-2526で、ファサードの完全性に関する課題とあわせて追跡されています。 - N+1 の自動検出。
.include(...)の指定漏れがあっても、Prisma Next は警告しません。回避策:コードレビューで.include(...)を意識して確認してください。lintsミドルウェア(prisma-next-runtimeを参照)は、よくある記述ミス(DELETE/UPDATEでのWHEREの指定漏れ、SELECTでのLIMITの指定漏れ)を検出します。
リファレンスファイル
このスキルは、必要に応じて読み込めるように分割されています。ターゲットごとのリファレンスパスは、各ターゲットのガイドに記載されています。
- Postgres —
postgres.md§ リファレンスファイル - Mongo —
mongo.md§ リファレンスファイル
チェックリスト
-
db.tsでアクティブなターゲットを確認し、対応するガイド(postgres.mdまたはmongo.md)を読み込みました。 - 複数の名前空間にまたがるコントラクトでは、同じ名前が複数の名前空間に存在する場合、
db.orm.<ns>.<Model>/db.sql.<ns>.<table>の座標を使用しました。 - 適切な方法を選びました(デフォルトでは ORM、ORM で表現できない形状には低レベルのビルダーを使用)。
- 1 行だけを読み取る場合は、
.all()ではなく.first()/.first({ pk })(Postgres)または.where({ ... }).first()(Mongo)を使用しました。 -
.all()は通常のawaitで処理しました(collect()/toArray()ヘルパーは使用していません)。ストリーミングが本当に必要な場合にのみfor awaitを使用し、同じ結果を 2 回反復していません。 - Mongo プロジェクトで
db.sqlを使用しておらず、Postgres SQL ビルダーを使うべき箇所でdb.queryを使用していません。 - 読み込んだガイドに記載されているターゲット固有のチェックリストを完了しました。