关于 prisma-next-queries
prisma-next-queries 是面向 Prisma Next 开发者的数据查询技能,介绍如何通过 ORM 或较底层的查询接口读写数据。内容覆盖关系加载、筛选、聚合、事务和结果消费方式,并区分 Postgres 与 Mongo 用法。
主要功能
- 01通过 ORM 读写与删除数据
- 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 角色的数据库(
asUser/asAnon/asServiceRole、RLS、auth.*管理员读取)进行查询 → 角色绑定接口请参阅prisma-next-supabase;本技能中的所有内容均适用于返回的RoleBoundDb。 - 用户想调试查询失败(结构化错误信封)→
prisma-next-debug。
选择目标
Prisma Next 在同一个 db 值上为每个目标提供两种查询方式,该值来自 src/prisma/db.ts。编写查询前,请先阅读 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 也没有对应 SQL 构建器的 db.query。
命名空间感知访问器
当契约声明了多个命名空间(例如 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>。这意味着三种消费方式都可行,其中最简洁的是标准写法:
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(...),将行缓冲到数组中。还有两种等效写法,在读起来更清晰时可以使用:
// `.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)之外,结果上还有两种单行快捷方法:
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.
对于确实只需读取单行的情况,优先使用集合级别的 .first()(它会在 Postgres 的 SQL 中添加 LIMIT 1),而不是 .all().first()(它会获取所有行,再丢弃其余行)。结果级别的辅助方法适用于你本来就需要完整结果,并且想要获取首行而不产生额外往返的情况。
结果只能消费一次。 每个 AsyncIterableResult 实例只能消费一次——通过 await、.toArray() 或 for await。尝试再次消费会抛出 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 上,由 façade 管理的连接池会让 Node 的事件循环保持运行;在 Mongo 上,由 façade 管理的 MongoClient 也会如此。完整模式(包括 await using)请参阅 prisma-next-runtime § 作为脚本运行(teardown)。
// 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)。 - 编写
collect()/toArray()辅助函数,将.all()转换为数组。.all()会返回AsyncIterableResult<Row>,它本身就是PromiseLike<Row[]>——直接执行await collection.all()即可得到Row[]。请参阅上文的消费结果。 - 重复消费
AsyncIterableResult。 每个结果只能使用一次。第二次消费会抛出RUNTIME.ITERATOR_CONSUMED。将结果缓冲到变量中一次,然后重复使用该变量。
各目标特有的问题请参阅对应的目标指南。
Prisma Next 目前尚不支持的功能
- 跨联接表的 N:M
.include()。 Contract IR 支持通过through联接表建立多对多关系,且 N:M 关系会作为有效的关系名称出现在 ORM 集合中。但是,对 N:M 关系使用.include()不会生成两步联接表连接——查询计划构建器只处理直接连接列(localColumn/targetColumn),忽略through元数据。尝试使用时,要么产生错误结果,要么报错。变通方法:通过db.sql.<table>表达 N:M 遍历,并显式联接联接表。 - N:M 嵌套变更。 对于通过 N:M 关系进行的嵌套创建/关联,
mutation-executor.ts会明确抛出'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>>,且不支持在数据库层排序或限制行数。因此,“按 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:绑定连接你控制的pg.Pool(参见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 });仅在必要时从内部包导入。相关问题与门面完整性差距一并记录在 LinearTML-2526中。 - 自动检测 N+1 问题。 Prisma Next 不会在缺少
.include(...)时发出警告。变通方法:在代码审查时留意.include(...);lints中间件(参见prisma-next-runtime)会捕获更常见的编写疏漏(DELETE/UPDATE缺少WHERE,SELECT缺少LIMIT)。
参考文件
此技能已拆分,以便按需加载。针对特定目标的参考路径见各目标指南:
- Postgres —
postgres.md§ Reference Files - Mongo —
mongo.md§ Reference Files
检查清单
- 已通过
db.ts确认当前目标,并加载了匹配的指南(postgres.md或mongo.md)。 - 对于多命名空间契约,当同一裸名称存在于多个命名空间时,使用了
db.orm.<ns>.<Model>/db.sql.<ns>.<table>坐标。 - 选择了正确的方式(默认使用 ORM;对于 ORM 无法表达的结构,使用较底层的构建器)。
- 单行读取使用了
.first()/.first({ pk })(Postgres)或.where({ ... }).first()(Mongo),而非.all()。 - 使用普通的
await消费.all()(不使用collect()/toArray()辅助函数)。仅在确实需要流式处理时使用for await,且绝不重复迭代同一结果。 - 未在 Mongo 项目中使用
db.sql,也未在应使用 Postgres SQL 构建器的场景中使用db.query。 - 已完成所加载指南中的目标专用检查清单。