npx skills add ...
npx skills add prisma/prisma-next --skill prisma-next-queries
npx skills add prisma/prisma-next --skill prisma-next-queries
Write Prisma Next queries for Postgres, SQLite, or Mongo — pick a lane (Postgres/SQLite `db.orm.<Model>` + `db.sql.<table>`; Mongo `db.orm.<root>` + `db.query.from(...)` pipeline builder), filter / project / sort / paginate, eager-load with `.include(...)`, Postgres/SQLite `db.transaction(...)`, Postgres/SQLite ORM `.aggregate(...)`, Mongo aggregations via query builder, namespace-aware accessors (`db.orm.<ns>.<Model>`, `db.sql.<ns>.<table>`). Triggers: query, where, match, select, project, orderBy, take, skip, include, lookup, first, all, count, aggregate, group, create, update, delete, upsert, returning, transaction, db.close, script teardown, variant, polymorphism, drizzle-style, kysely-style. Notes: `.all()` is a Thenable (just `await` it), iterators are single-use (`RUNTIME.ITERATOR_CONSUMED`), Postgres `count` is `number` while sum/avg/min/max are `number | null`, ranges use chained `.where()` or `and(...)` (no `.between(...)`).
Edit your data contract. Prisma handles the rest.
Once the contract is emitted and the DB is up to date, this skill covers everything you do with the data: reading, writing, eager-loading relations, aggregating, and the choice between the ORM and the lower-level query lane.
db.transaction(...) — Postgres and SQLite).count, sum, avg, …).prisma-next-contract.db.ts or add middleware → prisma-next-runtime.asUser / asAnon / asServiceRole, RLS, auth.* admin reads) → prisma-next-supabase for the role-binding surface; everything in this skill then applies to the returned RoleBoundDb.prisma-next-debug.Prisma Next ships two query lanes per target on the same db value from src/prisma/db.ts. Before writing queries, read db.ts and load the matching target guide:
Runtime import in db.ts | Load |
|---|---|
@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 — a Supabase RoleBoundDb is a Postgres surface (db.orm.<Model> + db.sql.<table>); bind a role first via prisma-next-supabase |
Both targets share the contract and connection on one db value. Reach for the ORM first; drop to the lower-level lane when the ORM can't express the shape. Lane choice is local — one query function picks one lane, not the whole app.
Do not mix target examples. Postgres uses PascalCase model roots (db.orm.User) and db.sql.user; Mongo uses lowercased plural roots (db.orm.users) and db.query.from('users'). There is no db.sql on Mongo and no db.query SQL-builder equivalent on Postgres.
When a contract declares more than one namespace (e.g. public and auth), models and tables are addressed by namespace coordinate:
db.orm.<namespace>.<Model> — e.g. db.orm.public.User, db.orm.auth.Userdb.sql.<namespace>.<table> — e.g. db.sql.public.users, db.sql.auth.usersThe flat db.orm.User / db.sql.users form still works for single-namespace contracts (or when all table names are unique across namespaces). When the same bare name appears in more than one namespace, you must use the namespace coordinate.
See postgres.md § Namespace-aware accessors for a worked example.
await, .toArray(), or for awaitCritical to get right early — on both Postgres and Mongo, .all() returns an AsyncIterableResult<Row>, which is both a PromiseLike<Row[]> and an AsyncIterable<Row>. That means three consumption forms all work, and the canonical one is the shortest:
You do not need a collect() / toArray() helper — await is enough. Internally await invokes the result's then(...), which buffers the rows into an array. Two equivalent alternatives exist for the cases where they read better:
Two single-row shortcuts also exist on the result, in addition to the collection-level .first() (which issues LIMIT 1 on Postgres):
For genuine single-row reads, prefer the collection-level .first() (which adds LIMIT 1 to the SQL on Postgres) over .all().first() (which fetches all rows and discards the rest). The result-level helpers are for cases where you already need the full result and want the first row without an extra round-trip.
The result is single-consumption. Each AsyncIterableResult instance can be consumed once — by await, by .toArray(), or by for await. Trying to consume it a second time throws RUNTIME.ITERATOR_CONSUMED. The fix is almost always to store the array in a variable on first consumption and reuse the variable:
If you've seen collect(...) / toArray(...) helpers in a codebase wrapping .all(), they're vestigial — await does the same thing for free. Remove them when you touch the surrounding code.
When the user is running a one-off tsx my-script.ts (not a long-lived server), call await db.close() at the end so the process exits cleanly — on Postgres the façade-owned pool keeps Node's event loop alive; on Mongo the façade-owned MongoClient does the same. See prisma-next-runtime § Running as a script (teardown) for the full pattern including await using.
db.ts and load the correct target guide (postgres.md or mongo.md).collect() / toArray() helper to convert .all() to an array. .all() returns an AsyncIterableResult<Row> which is a PromiseLike<Row[]> — await collection.all() directly yields Row[]. See Consuming the result above.AsyncIterableResult twice. Each result is single-use. The second consumer throws RUNTIME.ITERATOR_CONSUMED. Buffer once into a variable and reuse the variable.Target-specific pitfalls live in the per-target guides.
.include() across a junction table. The contract IR supports many-to-many relations with a through junction table, and N:M relations appear as valid relation names on the ORM collection. However, .include() on an N:M relation does not emit the two-step junction join — the query plan builder only handles the direct join columns (localColumn / targetColumn) and ignores the through metadata. Attempting it either produces wrong results or an error. Workaround: express the N:M traversal through db.sql.<table> with an explicit join on the junction table.mutation-executor.ts explicitly throws 'N:M nested mutations are not supported yet' for nested creates/links through an N:M relation.and / or / not combinators in the postgres façade. The combinators currently import from @prisma-next/sql-orm-client (an internal package). Workaround today: import them from @prisma-next/sql-orm-client directly, the way the example apps do. If you want them on @prisma-next/postgres/runtime, file a feature request via prisma-next-feedback..orderBy(...) / .take(...) on grouped aggregates (Postgres). db.orm.<Model>.groupBy(...).aggregate(...) materializes a Promise<Array<Group & Aggregates>> and exposes neither ordering nor row limits at the DB layer. Result: a "top-N groups by SUM" query falls back to JS-side sort + slice over the full grouped result, which is fine at small cardinalities and bad at scale. Workarounds: (a) drop to db.sql.<table> and write the GROUP BY + ORDER BY + LIMIT against the aggregated table directly; (b) live with the JS-side sort/slice if the grouped cardinality is bounded. File a feature request via prisma-next-feedback if this is hitting you in production.db.sql.raw(...)). Workaround: model the query through the SQL builder or — for shapes the builder can't yet express — file a feature request via prisma-next-feedback describing the shape so the team can decide whether to grow the builder or ship a raw lane..sql files compiled into typed callables). Not implemented. Workaround: stick to the SQL builder; for repeated queries, extract a function that returns the built plan and call db.runtime().execute(plan) at the call site. If you want a .sql-file compile path, file a feature request via prisma-next-feedback.EXPLAIN / query-plan inspection. Prisma Next does not expose an .explain() method. Workaround: connect a pg.Pool you control via the runtime's pg: binding (see prisma-next-runtime) and issue EXPLAIN ANALYZE through it. If you want a first-class plan-inspection surface, file a feature request via prisma-next-feedback..stream() cursor today. Workaround: paginate via .skip(n).take(m) for moderate sizes; for very large sets, hold a pg.Client from the runtime's pg: binding and stream through it directly. If you want a built-in streaming surface, file a feature request via prisma-next-feedback.db.$transaction([call1, call2])). Prisma Next runs each call sequentially. Workaround: wrap atomically-related work in db.transaction(async (tx) => { ... }) on Postgres. If you want batch-as-array semantics, file a feature request via prisma-next-feedback.@prisma-next/mongo/runtime does not expose db.transaction(...). Multi-document atomicity is not yet wrapped in the Prisma Next Mongo façade. Workaround: use the MongoDB driver's session API directly if you control the client binding (mongoClient: option). File a feature request via prisma-next-feedback if you need a first-class façade surface..aggregate(...) / .groupBy(...) on db.orm.<root>. Workaround: express aggregations through db.query.from(...).group(...).build() and runtime.execute(plan)..in, ranges, boolean composition) currently import from @prisma-next/mongo-query-ast/execution (MongoFieldFilter, etc.) — not yet re-exported on @prisma-next/mongo/runtime. Workaround: use object equality .where({ field: value }) where possible; import from the internal package only when necessary. Tracked alongside façade-completeness gaps in Linear TML-2526..include(...) is missing. Workaround: be deliberate about .include(...) in code review; the lints middleware (see prisma-next-runtime) catches the more common authoring slips (missing WHERE on a DELETE / UPDATE, missing LIMIT on a SELECT).This skill is split for selective loading. Target-specific reference paths live in the per-target guides:
postgres.md § Reference Filesmongo.md § Reference Filesdb.ts and loaded the matching guide (postgres.md or mongo.md).db.orm.<ns>.<Model> / db.sql.<ns>.<table> coordinates when the same bare name exists in more than one namespace..first() / .first({ pk }) (Postgres) or .where({ ... }).first() (Mongo) for single-row reads — not .all()..all() with plain await (not a collect() / toArray() helper). Used for await only when streaming is actually wanted, and never iterated the same result twice.db.sql on a Mongo project or db.query where the Postgres SQL builder is meant.// `.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);
}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.// 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;// 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();