Skip to main content

prisma-next-runtime

Wire the Prisma Next runtime — `db.ts` setup using `postgres<Contract>(...)` from `@prisma-next/postgres/runtime`, `sqlite<Contract>(...)` from `@prisma-next/sqlite/runtime`, or `mongo<Contract>(...)` from `@prisma-next/mongo/runtime`; middleware composition (telemetry from `@prisma-next/middleware-telemetry`; lints and budgets), `DATABASE_URL` config, per-environment branching, switching between Postgres, SQLite, and Mongo façades. Use for db.ts, postgres(), sqlite(), mongo(), middleware, telemetry, lints, budgets, DATABASE_URL, .env, connection pool, poolOptions, dev vs prod config, transactions, db.transaction, read replicas, multi-database, script won't exit, hangs, close connection, db.end, db.close, pool.end, [Symbol.asyncDispose], await using.

Aller à l'installation

Informations de source

Dépôt
prisma/prisma-next
Dernière activité de la source
15 juillet 2026 à 18:45
Langue détectée de SKILL.md
anglais
Étoiles
417
Forks
17

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
prisma-next-runtime
description
Wire the Prisma Next runtime — `db.ts` setup using `postgres<Contract>(...)` from `@prisma-next/postgres/runtime`, `sqlite<Contract>(...)` from `@prisma-next/sqlite/runtime`, or `mongo<Contract>(...)` from `@prisma-next/mongo/runtime`; middleware composition (telemetry from `@prisma-next/middleware-telemetry`; lints and budgets), `DATABASE_URL` config, per-environment branching, switching between Postgres, SQLite, and Mongo façades. Use for db.ts, postgres(), sqlite(), mongo(), middleware, telemetry, lints, budgets, DATABASE_URL, .env, connection pool, poolOptions, dev vs prod config, transactions, db.transaction, read replicas, multi-database, script won't exit, hangs, close connection, db.end, db.close, pool.end, [Symbol.asyncDispose], await using.
# Prisma Next — Runtime (`db.ts` Wiring) > **Edit your data contract. Prisma handles the rest.** This skill covers the **runtime entry point** — `db.ts` — and how to compose the database client with extensions, middleware, and environment configuration. ## When to Use - User is wiring up `db.ts` for the first time (post-init). - User wants to add middleware (telemetry, lints, budgets, custom). - User wants per-environment config (dev vs prod, multi-region). - User wants to switch between the Postgres, SQLite, and Mongo façades. - User wants to wrap operations in `db.transaction(...)` (Postgres and SQLite). - User is running a one-off script (`tsx my-script.ts`, Node CLI, CI task) and the process won't exit after queries finish, or they need script teardown (`db.close()`, `await using`). - User mentions: *db.ts, postgres(), mongo(), middleware, telemetry, lints, budgets, DATABASE_URL, .env, connection pool, poolOptions, dev vs prod, transactions, read replicas, multi-database, script won't exit, hangs, db.close, db.end, close connection, pool.end, await using*. ## When Not to Use - User wants to write queries → `prisma-next-queries`. - User is on Supabase — the `supabase()` role-first factory, `asUser(jwt)` / `asAnon()` / `asServiceRole()`, JWT config, RLS → `prisma-next-supabase`. - User wants to edit the contract → `prisma-next-contract`. - User wants to wire Prisma Next into a build tool (Vite plugin, Next.js, …) → `prisma-next-build`. - User wants to debug a connection / runtime error → `prisma-next-debug`. - User wants to file a bug or feature request → `prisma-next-feedback`. ## Key Concepts - **`db.ts` is the runtime entry point.** Imports the runtime factory from the `@prisma-next/<target>` façade (`@prisma-next/postgres/runtime`, `@prisma-next/sqlite/runtime`, or `@prisma-next/mongo/runtime`), the contract artefacts (`contract.json` + the `Contract` type from `contract.d.ts`), and any middleware. Exports a `db` value the rest of your app imports. - **The façade's runtime factory is the only surface user-authored `db.ts` imports from.** Each factory is a *default* export. For Postgres: `import postgres from '@prisma-next/postgres/runtime'`; SQLite: `import sqlite from '@prisma-next/sqlite/runtime'`; Mongo: `import mongo from '@prisma-next/mongo/runtime'`. The factory signature is `<Target><Contract>(options)` — a single type parameter (the `Contract` type from `contract.d.ts`), and one options object. - **Lazy connect.** The factory does not connect to the database synchronously. Static query surfaces (`db.sql`, `db.orm`) are available immediately; the driver / pool is instantiated on the first call that needs a runtime (or when you explicitly call `await db.connect({ url })`). This is why `db.ts` can be imported in modules that load before the env is ready. - **Middleware composes in order.** The first middleware in the `middleware: [...]` array runs *outermost* — it sees the operation first on the way in and last on the way out. Telemetry first means budget / lint failures show up inside telemetry spans. - **`prisma-next.config.ts` vs `.env`.** The config (`defineConfig({ contract, db, extensions, migrations })`) is for static project shape: contract path, installed extensions, migrations directory, default connection string. `.env` is for per-environment values (`DATABASE_URL`, secrets). The config reads `.env` automatically via `dotenv/config`. Hardcoding `DATABASE_URL` in the config file leaks credentials and bypasses per-env overrides. - **Build-system / dev-server integration is a separate skill.** `vite dev` auto-emit lives in `prisma-next-build`. The runtime side (this skill) reads `contract.json` / `contract.d.ts` regardless of how they got onto disk, so the two skills compose cleanly. ## Workflow — Basic `db.ts` The concept: `db.ts` is the seam between the emitted contract artefacts (target-shaped) and the runtime that executes queries against them. Three imports are load-bearing — the runtime factory, the `Contract` type (so the static query surfaces are typed), and the JSON artefact (so the runtime validates the structure at construct time). `init` scaffolds something like this (for `--target postgres`): ```typescript // src/prisma/db.ts import postgres from '@prisma-next/postgres/runtime'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; export const db = postgres<Contract>({ contractJson, url: process.env['DATABASE_URL'], }); ``` (`init` currently scaffolds at `prisma/db.ts` instead — see TML-2532 in `prisma-next-quickstart`. The canonical path is `src/prisma/db.ts`; the rest of `src/` imports from `./prisma/db` or `../prisma/db` depending on depth.) Three things to know: - **`<Contract>` type parameter is load-bearing.** Without it, the static surfaces collapse to a generic shape and you lose autocomplete on model names. Always import `Contract` from the emitted `./contract.d.ts`. - **`with { type: 'json' }` is required.** Node's ESM JSON-import-attribute spec. Without it, the import errors. - **`url` is optional at construct time.** If `DATABASE_URL` is not set when `db.ts` loads, the factory still returns a client; you can call `await db.connect({ url })` later. The factory throws lazily — only when a runtime is actually needed. The Mongo façade has the same construction shape — `import mongo from '@prisma-next/mongo/runtime'` — and the same `db.connect(...)` / `db.close()` lifecycle methods. **The Mongo façade does not expose `db.transaction(...)`.** See *What Prisma Next doesn't do yet* for the workaround. **The ORM surface differs in one place: keys.** On Mongo, `db.orm` is keyed by the collection's storage name (from `@@map(...)`, or the lowercased model name if no `@@map` is set), not by the PSL model name — so `model User { … @@map("users") }` is reached at `db.orm.users`, not `db.orm.User`. The SQL builder lane (`db.sql.<table>`) doesn't exist on Mongo at all (`db.sql` is `undefined`). See `prisma-next-queries` § *MongoDB ORM addressing* for the full rule and a rewrite recipe for SQL-target examples. ## Workflow — Running as a script (teardown) The concept: short scripts that connect, query, then expect the process to exit will **hang on Postgres** because the façade-owned `pg.Pool` keeps Node's event loop alive. The data round-trip succeeds; the script never exits. Call `await db.close()` before the script returns (or use `await using` **at the top of a script module** so teardown runs when the module exits — see the block-scope warning below for why this matters). **Plain shape** — export `db` from `db.ts`, import it in the script, close at the end: ```typescript // src/scripts/hello.ts import { db } from '../prisma/db'; const created = await db.orm.User.create({ email: 'alice@example.com', name: 'Alice' }); const read = await db.orm.User.first(); console.log({ created, read }); await db.close(); ``` **TS 5.2+ idiomatic shape** — construct the client at the **top of a script module** and let `[Symbol.asyncDispose]` call `close()` when the module exits: ```typescript // src/scripts/hello.ts — top-level await in a script module import postgres from '@prisma-next/postgres/runtime'; import type { Contract } from '../prisma/contract.d'; import contractJson from '../prisma/contract.json' with { type: 'json' }; await using db = postgres<Contract>({ contractJson, url: process.env.DATABASE_URL! }); const user = await db.orm.User.first(); console.log(user); // db.close() runs automatically when the script module exits. ``` ### `await using` is **block-scoped** — do not put it inside a request handler This is the most important rule in this section. `await using db = postgres(...)` disposes when the *enclosing block* exits. In a script module, that block is the module body and disposal fires at process exit — fine. In a request handler, the enclosing block is the handler function, so disposal fires **after every request** — a fresh `pg.Pool` per call, TCP-connect storm, hot loop tearing connections up and down. ```typescript // DO NOT do this — closes the pool after every request. app.get('/users', async (req, res) => { await using db = postgres<Contract>({ contractJson, url: process.env.DATABASE_URL! }); const users = await db.orm.User.all(); res.json(users); }); ``` The right server pattern is a **module-level singleton** in `db.ts`, imported by handlers, never closed during the process lifetime: ```typescript // src/prisma/db.ts — constructed once, lives for the process export const db = postgres<Contract>({ contractJson, url: process.env.DATABASE_URL }); // src/routes/users.ts import { db } from '../prisma/db'; app.get('/users', async (req, res) => { const users = await db.orm.User.all(); res.json(users); }); ``` Servers (HTTP handlers, workers in a request loop) **do not call `db.close()`** at all in steady state. The pool stays open for the process lifetime. `db.close()` and `await using` are for short-lived scripts — `tsx my-script.ts`, Node CLI commands, CI tasks, one-off seed runs — not for code that runs inside a request loop. **Semantics:** - **`close()` is idempotent.** Calling it twice is a no-op. - **`close()` is terminal.** There is no reconnect on a closed `db` — construct a new client if you need another connection. After close, `db.runtime()`, `db.connect(...)`, `db.transaction(...)`, and `db.prepare(...)` reject with `Error('<target> client is closed')` (e.g. `'Postgres client is closed'`, `'SQLite client is closed'`, `'Mongo client is closed'`). - **`close()` does not abort in-flight queries.** `await` outstanding work before calling `close()`. Async iterators from `db.runtime().execute(plan)` and `PreparedStatement` handles held after `close()` fail on their next call. - **Ownership.** `close()` releases only what the façade constructed (`pg.Pool` from `{ url }`, `MongoClient` from `{ url }` / `{ uri, dbName }`, SQLite handle from `{ path }`). If you supplied your own `pg.Pool` / `pg.Client` (Postgres `pg:` option), `mongodb.MongoClient` (Mongo `mongoClient:` option), or a pre-built `binding`, `db.close()` does **not** touch those — you own their lifecycle. **`db.end()` does not exist.** The universal `node-postgres` name is `pool.end()` on a `pg.Pool`; the Prisma Next runtime client is not a `pg.Pool`. The right call is `await db.close()`. ## Workflow — Telemetry middleware The concept: telemetry middleware sees every operation and emits a structured event for each (start, success, error). Pair the events with your observability stack's collector. ```typescript import postgres from '@prisma-next/postgres/runtime'; import { createTelemetryMiddleware } from '@prisma-next/middleware-telemetry'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; export const db = postgres<Contract>({ contractJson, url: process.env['DATABASE_URL'], middleware: [ createTelemetryMiddleware({ onEvent: (event) => { // forward to your collector, log, etc. }, }), ], }); ``` `createTelemetryMiddleware` is shipped as a separate user-installable package (`@prisma-next/middleware-telemetry`), not as a `/middleware` subpath of the postgres façade. Install it directly. Run `pnpm ls @prisma-next/middleware-telemetry` to confirm it's on the lockfile. ## Workflow — Lints and budgets middleware The concept: lints catch authoring mistakes that survive type-check (e.g. `DELETE` without a `WHERE`, `SELECT` without a `LIMIT` on a large table); budgets enforce row-count and latency ceilings at runtime. Both surface findings through the structured-error envelope so an agent can branch on the code. These ship in the underlying SQL runtime package (`@prisma-next/sql-runtime`) and are *not* yet re-exported from the postgres façade — see *What Prisma Next doesn't do yet*. The example apps under `examples/prisma-next-demo/src/prisma/db.ts` show the canonical import. ```typescript import postgres from '@prisma-next/postgres/runtime'; import { budgets, lints } from '@prisma-next/sql-runtime'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; export const db = postgres<Contract>({ contractJson, url: process.env['DATABASE_URL'], middleware: [ lints({ severities: { selectStar: 'warn', noLimit: 'error', deleteWithoutWhere: 'error', updateWithoutWhere: 'error', readOnlyMutation: 'error', }, }), budgets({ maxRows: 10_000, defaultTableRows: 10_000, tableRows: { user: 10_000, post: 50_000 }, maxLatencyMs: 1_000, severities: { rowCount: 'error', latency: 'warn' }, }), ], }); ``` For the full option surface, read the source: `packages/2-sql/5-runtime/src/middleware/lints.ts` and `.../budgets.ts`. The `severities` keys (`selectStar`, `noLimit`, `deleteWithoutWhere`, `updateWithoutWhere`, `readOnlyMutation` for lints; `rowCount`, `latency` for budgets) are the source of truth; do not extrapolate to a key that ripgrep can't find. ## Workflow — Compose multiple middleware ```typescript middleware: [ createTelemetryMiddleware({ onEvent }), // outermost — sees all sub-failures as inner errors lints({ severities: { noLimit: 'error' } }), budgets({ maxLatencyMs: 5_000 }), // innermost — runs closest to the driver ], ``` Order matters: outermost wraps. Telemetry first means budget / lint failures are captured as spans (the agent can correlate the lint code with the operation in the same trace). ## Workflow — Configure the connection
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub