mcpField
P
prisma-next-queries

prisma-next-queries

github.com/prisma数据与数据库4112.1k 次安装9 周前更新
代码仓库

关于 prisma-next-queries

prisma-next-queries 是面向 Prisma Next 开发者的数据查询技能,介绍如何通过 ORM 或较底层的查询接口读写数据。内容覆盖关系加载、筛选、聚合、事务和结果消费方式,并区分 Postgres 与 Mongo 用法。

主要功能

  1. 01通过 ORM 读写与删除数据
  2. 02使用底层查询接口
  3. 03加载关联并筛选排序
  4. 04分页、投影与聚合
  5. 05处理异步查询结果与事务

适用场景

  1. 01查询并筛选模型记录
  2. 02读取记录时加载关联数据
  3. 03对查询结果分页或聚合
  4. 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/runtimepostgres.md — db.orm.<Model> + db.sql.<table>
@prisma-next/mongo/runtimemongo.md — db.orm.<root> + db.query.from(...)
@prisma-next/extension-supabase/runtimepostgres.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();

常见问题(跨目标)

  1. 在 Mongo 项目中使用 Postgres 示例(或反之)。 检查 db.ts,并加载正确的目标指南(postgres.md 或 mongo.md)。
  2. 编写 collect() / toArray() 辅助函数,将 .all() 转换为数组。 .all() 会返回 AsyncIterableResult<Row>,它本身就是 PromiseLike<Row[]>——直接执行 await collection.all() 即可得到 Row[]。请参阅上文的消费结果。
  3. 重复消费 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 });仅在必要时从内部包导入。相关问题与门面完整性差距一并记录在 Linear TML-2526 中。
  • 自动检测 N+1 问题。 Prisma Next 不会在缺少 .include(...) 时发出警告。变通方法:在代码审查时留意 .include(...);lints 中间件(参见 prisma-next-runtime)会捕获更常见的编写疏漏(DELETE / UPDATE 缺少 WHERE,SELECT 缺少 LIMIT)。

参考文件

此技能已拆分,以便按需加载。针对特定目标的参考路径见各目标指南:

检查清单

  • 已通过 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。
  • 已完成所加载指南中的目标专用检查清单。

评论

支持 Markdown