prisma-next-runtime

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Prisma Next — Runtime (
db.ts
Wiring)

Prisma Next — 运行时(
db.ts
配置)

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.
编辑你的数据契约,其余工作由Prisma处理。
本技能涵盖运行时入口文件——
db.ts
——以及如何通过扩展、中间件和环境配置组合数据库客户端。

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 and Mongo façades.
  • User wants to wrap operations in
    db.transaction(...)
    .
  • 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.
  • 用户首次配置
    db.ts
    (初始化后)。
  • 用户想要添加中间件(遥测、代码检查、预算控制、自定义中间件)。
  • 用户需要多环境配置(开发/生产环境、多区域)。
  • 用户想要在Postgres与Mongo外观层之间切换。
  • 用户想要将操作包装在
    db.transaction(...)
    中。
  • 用户运行一次性脚本(
    tsx my-script.ts
    、Node CLI、CI任务),查询完成后进程无法退出,或需要脚本清理(
    db.close()
    await using
    )。
  • 用户提及以下内容: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 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
    .
  • 用户想要编写查询 → 使用
    prisma-next-queries
  • 用户想要编辑契约 → 使用
    prisma-next-contract
  • 用户想要将Prisma Next集成到构建工具(Vite插件、Next.js等)→ 使用
    prisma-next-build
  • 用户想要调试连接/运行时错误 → 使用
    prisma-next-debug
  • 用户想要提交Bug或功能请求 → 使用
    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
    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.
    Postgres:
    import postgres from '@prisma-next/postgres/runtime'
    . The factory is a default export. The Postgres runtime factory's signature is
    postgres<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.
  • db.ts
    是运行时入口点
    。从
    @prisma-next/<target>
    外观层(
    @prisma-next/postgres/runtime
    @prisma-next/mongo/runtime
    )导入运行时工厂,导入契约制品(
    contract.json
    +
    contract.d.ts
    中的
    Contract
    类型),以及任何中间件。导出
    db
    供应用程序其他部分导入使用。
  • 外观层的运行时工厂是用户编写的
    db.ts
    唯一需要导入的接口
    。Postgres:
    import postgres from '@prisma-next/postgres/runtime'
    。工厂是默认导出。Postgres运行时工厂的签名为
    postgres<Contract>(options)
    ——一个类型参数(来自
    contract.d.ts
    Contract
    类型),以及一个选项对象。
  • 延迟连接。工厂不会同步连接到数据库。静态查询接口(
    db.sql
    db.orm
    )可立即使用;驱动/连接池会在首次需要运行时的调用中实例化(或当你显式调用
    await db.connect({ url })
    时)。这就是为什么
    db.ts
    可以在环境变量准备好之前导入到模块中的原因。
  • 中间件按顺序组合
    middleware: [...]
    数组中的第一个中间件运行在最外层——它在进入时最先看到操作,在退出时最后看到操作。遥测放在最前面意味着预算/代码检查失败会显示在遥测跨度内。
  • prisma-next.config.ts
    .env
    的区别
    。配置文件(
    defineConfig({ contract, db, extensions, migrations })
    )用于静态项目结构:契约路径、已安装的扩展、迁移目录、默认连接字符串。
    .env
    用于多环境值(
    DATABASE_URL
    、密钥)。配置文件会通过
    dotenv/config
    自动读取
    .env
    。在配置文件中硬编码
    DATABASE_URL
    会泄露凭据并绕过多环境覆盖。
  • 构建系统/开发服务器集成是单独的技能
    vite dev
    自动生成功能在
    prisma-next-build
    中。运行时部分(本技能)会读取磁盘上的
    contract.json
    /
    contract.d.ts
    ,无论它们是如何生成的,因此这两个技能可以完美组合。

Workflow — Basic
db.ts

工作流——基础
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 shape —
import mongo from '@prisma-next/mongo/runtime'
— and the same
db
surface.
核心思路:
db.ts
是生成的契约制品(目标特定形状)与执行查询的运行时之间的衔接层。三个导入是必需的——运行时工厂、
Contract
类型(以便静态查询接口具备类型提示)、JSON制品(以便运行时在构造时验证结构)。
init
命令会生成类似以下的代码(针对
--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
命令会在
prisma/db.ts
生成代码——详见
prisma-next-quickstart
中的TML-2532。标准路径为
src/prisma/db.ts
src/
下的其他模块根据层级从
./prisma/db
../prisma/db
导入。)
需要了解三点:
  • <Contract>
    类型参数是必需的
    。如果没有它,静态接口会退化为通用形状,你将失去模型名称的自动补全功能。务必从生成的
    ./contract.d.ts
    中导入
    Contract
  • with { type: 'json' }
    是必需的
    。这是Node的ESM JSON导入属性规范。如果没有它,导入会报错。
  • 构造时
    url
    是可选的
    。如果
    db.ts
    加载时
    DATABASE_URL
    未设置,工厂仍会返回客户端;你可以稍后调用
    await db.connect({ url })
    。工厂会延迟抛出错误——仅当实际需要运行时时才会抛出。
Mongo外观层具有相同的结构——
import mongo from '@prisma-next/mongo/runtime'
——以及相同的
db
接口。

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.
核心思路:连接、查询后期望进程退出的短脚本在Postgres上会挂起,因为外观层拥有的
pg.Pool
会保持Node事件循环活跃。数据往返成功,但脚本永远不会退出。在脚本返回前调用
await db.close()
(或在脚本模块的顶部使用
await using
,以便模块退出时自动执行清理——请参阅下面的块作用域警告了解原因)。
基础写法——从
db.ts
导出
db
,在脚本中导入,最后关闭:
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+ 惯用写法——在脚本模块的顶部构造客户端,让
[Symbol.asyncDispose]
在模块退出时调用
close()
typescript
// src/scripts/hello.ts — 脚本模块中的顶层await
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()。

await using
is block-scoped — do not put it inside a request handler

await using
块作用域——不要将其放在请求处理程序内部

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()
.
这是本节最重要的规则。
await using db = postgres(...)
会在封闭块退出时释放资源。在脚本模块中,该块是模块主体,释放会在进程退出时触发——没问题。在请求处理程序中,封闭块是处理程序函数,因此释放会在每个请求后触发——每次调用都会创建新的
pg.Pool
,导致TCP连接风暴,频繁创建和销毁连接。
typescript
// 不要这样做——每个请求后都会关闭连接池。
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);
});
正确的服务器模式是在
db.ts
中创建模块级单例,由处理程序导入,进程运行期间永不关闭:
typescript
// src/prisma/db.ts — 仅构造一次,伴随进程生命周期
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);
});
服务器(HTTP处理程序、请求循环中的工作进程)在稳定状态下完全不调用
db.close()
。连接池会在进程生命周期内保持打开。
db.close()
await using
适用于短生命周期脚本——
tsx my-script.ts
、Node CLI命令、CI任务、一次性种子数据运行——不适用于请求循环内运行的代码。
语义说明
  • close()
    是幂等的
    。调用两次不会产生副作用。
  • close()
    是终结性的
    。关闭后的
    db
    无法重新连接——如果需要另一个连接,请构造新的客户端。关闭后,
    db.runtime()
    db.connect(...)
    db.transaction(...)
    db.prepare(...)
    会抛出
    Error('<target> client is closed')
    (例如
    'Postgres client is closed'
    'SQLite client is closed'
    'Mongo client is closed'
    )。
  • close()
    不会中止正在进行的查询
    。调用
    close()
    前请等待未完成的操作完成。
    db.runtime().execute(plan)
    返回的异步迭代器以及
    close()
    后持有的
    PreparedStatement
    句柄会在下次调用时失败。
  • 所有权
    close()
    仅释放外观层构造的资源(来自
    { url }
    pg.Pool
    、来自
    { url }
    /
    { uri, dbName }
    MongoClient
    、来自
    { path }
    的SQLite句柄)。如果你提供了自己的
    pg.Pool
    /
    pg.Client
    (Postgres的
    pg:
    选项)、
    mongodb.MongoClient
    (Mongo的
    mongoClient:
    选项)或预构建的
    binding
    db.close()
    不会触碰这些资源——你需要管理它们的生命周期。
db.end()
不存在
。通用的
node-postgres
名称是
pg.Pool
上的
pool.end()
;Prisma Next运行时客户端不是
pg.Pool
。正确的调用是
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.
核心思路:遥测中间件会监控每个操作,并为每个操作发出结构化事件(开始、成功、错误)。将事件与你的可观测性堆栈收集器配对使用。
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) => {
        // 转发到你的收集器、日志等
      },
    }),
  ],
});
createTelemetryMiddleware
作为独立的用户可安装包(
@prisma-next/middleware-telemetry
)发布,而不是postgres外观层的
/middleware
子路径。请直接安装它。运行
pnpm ls @prisma-next/middleware-telemetry
确认它已在锁文件中。

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',
        unindexedPredicate: 'warn',
      },
    }),
    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
,
unindexedPredicate
for lints;
rowCount
,
latency
for budgets) are the source of truth; do not extrapolate to a key that ripgrep can't find.
核心思路:代码检查会捕获类型检查后仍存在的编写错误(例如不带
WHERE
DELETE
、大表上不带
LIMIT
SELECT
);预算控制在运行时强制执行行数和延迟上限。两者都会通过结构化错误包呈现结果,以便代理可以根据代码分支处理。
这些中间件包含在底层SQL运行时包(
@prisma-next/sql-runtime
)中,目前尚未从postgres外观层重新导出——请参阅Prisma Next目前不支持的功能
examples/prisma-next-demo/src/prisma/db.ts
下的示例应用展示了标准导入方式。
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',
        unindexedPredicate: 'warn',
      },
    }),
    budgets({
      maxRows: 10_000,
      defaultTableRows: 10_000,
      tableRows: { user: 10_000, post: 50_000 },
      maxLatencyMs: 1_000,
      severities: { rowCount: 'error', latency: 'warn' },
    }),
  ],
});
完整的选项接口,请阅读源代码:
packages/2-sql/5-runtime/src/middleware/lints.ts
.../budgets.ts
severities
的键(代码检查的
selectStar
noLimit
deleteWithoutWhere
updateWithoutWhere
readOnlyMutation
unindexedPredicate
;预算控制的
rowCount
latency
)是权威来源;不要推断ripgrep无法找到的键。

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).
typescript
middleware: [
  createTelemetryMiddleware({ onEvent }),  // 最外层——捕获所有子中间件的错误作为内部错误
  lints({ severities: { noLimit: 'error' } }),
  budgets({ maxLatencyMs: 5_000 }),         // 最内层——最接近驱动运行
],
顺序很重要:最外层包裹其他中间件。将遥测放在最前面意味着预算/代码检查失败会被捕获为跨度(代理可以在同一跟踪中将代码检查代码与操作关联起来)。

Workflow — Configure the connection

工作流——配置连接

The concept: the runtime takes one of three binding shapes —
url
,
pg
(a pre-constructed
pg.Pool
or
pg.Client
), or
binding
(an explicit kind tag). They're mutually exclusive. The
pg
form is for projects that already manage their own pool (e.g. a Lambda layer);
url
is the default. Pool tuning is
poolOptions.connectionTimeoutMillis
/
poolOptions.idleTimeoutMillis
not
driverOptions
.
typescript
// Default — URL string, factory constructs the pool.
postgres<Contract>({
  contractJson,
  url: process.env['DATABASE_URL'],
  poolOptions: {
    connectionTimeoutMillis: 20_000,
    idleTimeoutMillis: 30_000,
  },
});

// BYO pool — pass a pg.Pool you already created.
import { Pool } from 'pg';
const pool = new Pool({ connectionString: process.env['DATABASE_URL'] });
postgres<Contract>({ contractJson, pg: pool });
The
url
and
pg
keys are mutually exclusive at the type level; passing both errors.
DATABASE_URL
lives in
.env
. The CLI reads it for emit / verify / migration commands; the runtime reads it through
process.env
at
db.ts
load time.
核心思路:运行时接受三种绑定形状——
url
pg
(预构造的
pg.Pool
pg.Client
)、
binding
(显式类型标签)。它们互斥。
pg
形式适用于已经管理自己连接池的项目(例如Lambda层);
url
是默认选项。连接池调优使用
poolOptions.connectionTimeoutMillis
/
poolOptions.idleTimeoutMillis
——不是
driverOptions
typescript
// 默认——URL字符串,工厂构造连接池。
postgres<Contract>({
  contractJson,
  url: process.env['DATABASE_URL'],
  poolOptions: {
    connectionTimeoutMillis: 20_000,
    idleTimeoutMillis: 30_000,
  },
});

// 自带连接池——传入你已创建的pg.Pool。
import { Pool } from 'pg';
const pool = new Pool({ connectionString: process.env['DATABASE_URL'] });
postgres<Contract>({ contractJson, pg: pool });
url
pg
键在类型层面互斥;同时传入两者会报错。
DATABASE_URL
存储在
.env
中。CLI在生成/验证/迁移命令时读取它;运行时在
db.ts
加载时通过
process.env
读取它。

Workflow — Per-environment config (dev vs prod)

工作流——多环境配置(开发/生产)

The concept: one
DATABASE_URL
per environment; the rest of the
db.ts
shape is the same. For middleware divergence (e.g. strict lints in dev only), branch in
db.ts
on
process.env['NODE_ENV']
.
typescript
const isProd = process.env['NODE_ENV'] === 'production';

export const db = postgres<Contract>({
  contractJson,
  url: process.env['DATABASE_URL'],
  middleware: isProd
    ? [createTelemetryMiddleware({ onEvent })]
    : [
        createTelemetryMiddleware({ onEvent }),
        lints({ severities: { noLimit: 'error', deleteWithoutWhere: 'error' } }),
      ],
});
.env
for local; the deploy platform's secrets for prod. Never commit
.env
.
核心思路:每个环境对应一个
DATABASE_URL
db.ts
的其余结构保持不变。对于中间件差异(例如仅在开发环境启用严格代码检查),在
db.ts
中根据
process.env['NODE_ENV']
分支处理。
typescript
const isProd = process.env['NODE_ENV'] === 'production';

export const db = postgres<Contract>({
  contractJson,
  url: process.env['DATABASE_URL'],
  middleware: isProd
    ? [createTelemetryMiddleware({ onEvent })]
    : [
        createTelemetryMiddleware({ onEvent }),
        lints({ severities: { noLimit: 'error', deleteWithoutWhere: 'error' } }),
      ],
});
本地使用
.env
;生产环境使用部署平台的密钥。永远不要提交
.env
文件。

Workflow — Transactions

工作流——事务

The concept:
db.transaction(fn)
opens a transaction, gives the callback a
tx
context with the same
sql
/
orm
surfaces as
db
, and commits on successful return / rolls back on any thrown error. Inside the callback, use
tx.sql
and
tx.orm
instead of
db.sql
/
db.orm
so the writes ride the transaction.
typescript
await db.transaction(async (tx) => {
  const user = await tx.orm.User.create({ email: 'alice@example.com' });
  await tx.orm.Post.create({ userId: user.id, title: 'hello' });
  // If either call throws, both inserts roll back.
});
The callback returns whatever you return from it — the transaction wrapper passes it through. The
tx
object exposes
execute(plan)
for SQL-builder plans inside the transaction.
核心思路:
db.transaction(fn)
会开启一个事务,向回调函数提供一个
tx
上下文,该上下文具有与
db
相同的
sql
/
orm
接口,并在回调成功返回时提交事务,在抛出任何错误时回滚事务。在回调内部,请使用
tx.sql
tx.orm
而非
db.sql
/
db.orm
,以便写入操作在事务中执行。
typescript
await db.transaction(async (tx) => {
  const user = await tx.orm.User.create({ email: 'alice@example.com' });
  await tx.orm.Post.create({ userId: user.id, title: 'hello' });
  // 如果任一调用抛出错误,两次插入都会回滚。
});
回调会返回你在其中返回的任何值——事务包装器会将其传递出去。
tx
对象暴露
execute(plan)
用于事务内的SQL构建器计划。

Workflow — Switch between Postgres and Mongo

工作流——在Postgres与Mongo之间切换

The concept: the façade selection is baked into
db.ts
(
@prisma-next/postgres
vs
@prisma-next/mongo
) and
prisma-next.config.ts
(which
defineConfig
you import from). To switch a project's target, re-run
prisma-next init
in the same directory and pick the other target — the init flow detects the existing scaffold and prompts to reinit (
--force
skips the prompt). PN re-scaffolds
prisma-next.config.ts
and
db.ts
for the new façade. The contract source needs to be re-authored for the new target's idioms (Mongo expresses nested documents; Postgres expresses relations).
After the switch:
typescript
// src/prisma/db.ts (Mongo)
import mongo from '@prisma-next/mongo/runtime';
import type { Contract } from './contract.d';
import contractJson from './contract.json' with { type: 'json' };

export const db = mongo<Contract>({ contractJson, url: process.env['DATABASE_URL'] });
The
db.sql
/
db.orm
surfaces stay the same in name; the operators each surface exposes are target-shaped (Mongo has no
JOIN
).
核心思路:外观层选择固化在
db.ts
@prisma-next/postgres
vs
@prisma-next/mongo
)和
prisma-next.config.ts
(你导入的
defineConfig
)中。要切换项目的目标,请在同一目录中重新运行
prisma-next init
并选择另一个目标——初始化流程会检测到现有脚手架并提示重新初始化(
--force
跳过提示)。PN会为新外观层重新生成
prisma-next.config.ts
db.ts
。契约源需要针对新目标的特性重新编写(Mongo支持嵌套文档;Postgres支持关联关系)。
切换后:
typescript
// src/prisma/db.ts (Mongo)
import mongo from '@prisma-next/mongo/runtime';
import type { Contract } from './contract.d';
import contractJson from './contract.json' with { type: 'json' };

export const db = mongo<Contract>({ contractJson, url: process.env['DATABASE_URL'] });
db.sql
/
db.orm
接口名称保持不变,但每个接口暴露的操作符是目标特定的(Mongo没有
JOIN
)。

Workflow — Build-system / dev-server integration

工作流——构建系统/开发服务器集成

If you want contract artefacts to re-emit automatically while the dev server is running (instead of running
prisma-next contract emit
by hand each time the contract source changes), reach for the build-tool plugin from
prisma-next-build
:
  • Vite: install
    @prisma-next/vite-plugin-contract-emit
    and register
    prismaVitePlugin('prisma-next.config.ts')
    in
    vite.config.ts
    .
  • Next.js, Webpack, esbuild, Rollup, Turbopack: no first-party plugin yet — the workaround is a
    prebuild
    script that runs
    prisma-next contract emit
    . See
    prisma-next-build
    for the walkthrough.
The runtime side (this skill) is the same regardless:
db.ts
reads
contract.json
+
contract.d.ts
from disk. The build-system plugin's job is to keep those files current during development.
如果你希望契约制品在开发服务器运行时自动重新生成(而不是每次契约源更改时手动运行
prisma-next contract emit
),请使用
prisma-next-build
中的构建工具插件:
  • Vite:安装
    @prisma-next/vite-plugin-contract-emit
    并在
    vite.config.ts
    中注册
    prismaVitePlugin('prisma-next.config.ts')
  • Next.js、Webpack、esbuild、Rollup、Turbopack:目前没有官方插件——解决方法是添加
    prebuild
    脚本运行
    prisma-next contract emit
    。请参阅
    prisma-next-build
    获取详细步骤。
运行时部分(本技能)保持不变:
db.ts
从磁盘读取
contract.json
+
contract.d.ts
。构建系统插件的作用是在开发期间保持这些文件更新。

Common Pitfalls

常见陷阱

  1. Hardcoding
    DATABASE_URL
    in
    prisma-next.config.ts
    .
    Leaks credentials; bypasses per-environment overrides. Use
    .env
    .
  2. Omitting the
    <Contract>
    type parameter
    in
    postgres<Contract>(...)
    . Without it, static surfaces collapse to a generic shape and you lose autocomplete for models. There is no second type parameter — the older two-param signature (
    postgres<Contract, TypeMaps>
    ) is gone.
  3. Forgetting
    with { type: 'json' }
    on the contract import.
    Required by Node's ESM JSON-import-attribute spec.
  4. Middleware order matters. Outermost wraps. Put telemetry first if you want it to capture inner-middleware errors.
  5. Importing middleware from a non-existent façade subpath.
    @prisma-next/postgres/middleware
    does not exist. Telemetry comes from
    @prisma-next/middleware-telemetry
    ; lints / budgets come from
    @prisma-next/sql-runtime
    today (see What Prisma Next doesn't do yet).
  6. Confabulating lint / budget option names. Lints take
    severities
    (with the six keys above), not
    requireWhere
    /
    maxRowsWithoutLimit
    . Budgets use
    maxLatencyMs
    (not
    maxDurationMs
    ) plus
    maxRows
    /
    defaultTableRows
    /
    tableRows
    . When in doubt, read the source.
  7. Switching targets without re-emitting. The contract artefacts are target-shaped; emit after the target change.
  8. Script hangs after queries finish on Postgres. The
    pg.Pool
    keeps Node's event loop alive. Solution:
    await db.close()
    before the script returns, or
    await using db = postgres<Contract>(...)
    at the top of a script module. Do not put
    await using db = postgres(...)
    inside a request handler — it's block-scoped and would close the pool after every request. The right server pattern is a module-level singleton in
    db.ts
    that lives for the process lifetime.
  1. prisma-next.config.ts
    中硬编码
    DATABASE_URL
    。会泄露凭据;绕过多环境覆盖。请使用
    .env
  2. postgres<Contract>(...)
    中省略
    <Contract>
    类型参数
    。没有它,静态接口会退化为通用形状,你将失去模型的自动补全功能。不存在第二个类型参数——旧的双参数签名(
    postgres<Contract, TypeMaps>
    )已被移除。
  3. 忘记在契约JSON导入中添加
    with { type: 'json' }
    。这是Node的ESM JSON导入属性规范要求的。
  4. 中间件顺序错误。最外层包裹其他中间件。如果你想要遥测捕获内部中间件错误,请将遥测放在最前面。
  5. 从不存在的外观层子路径导入中间件
    @prisma-next/postgres/middleware
    不存在。遥测来自
    @prisma-next/middleware-telemetry
    ;目前代码检查/预算控制来自
    @prisma-next/sql-runtime
    (请参阅Prisma Next目前不支持的功能)。
  6. 错误推断代码检查/预算控制选项名称。代码检查接受
    severities
    (包含上述六个键),而非
    requireWhere
    /
    maxRowsWithoutLimit
    。预算控制使用
    maxLatencyMs
    (而非
    maxDurationMs
    )以及
    maxRows
    /
    defaultTableRows
    /
    tableRows
    。如有疑问,请阅读源代码。
  7. 切换目标后未重新生成契约。契约制品是目标特定形状的;切换目标后请重新生成。
  8. Postgres上查询完成后脚本挂起
    pg.Pool
    会保持Node事件循环活跃。解决方法:脚本返回前调用
    await db.close()
    ,或在脚本模块顶部使用
    await using db = postgres<Contract>(...)
    。不要将
    await using db = postgres(...)
    放在请求处理程序内部——它是块作用域的,会在每个请求后关闭连接池。正确的服务器模式是在
    db.ts
    中创建模块级单例,伴随进程生命周期。

What Prisma Next doesn't do yet

Prisma Next目前不支持的功能

  • @prisma-next/postgres/middleware
    subpath.
    The postgres façade re-exports the runtime factory (
    ./runtime
    ), config (
    ./config
    ), contract-builder (
    ./contract-builder
    ), control (
    ./control
    ), family (
    ./family
    ), target (
    ./target
    ), and serverless (
    ./serverless
    ) — but not middleware. Today's workaround: import
    lints
    and
    budgets
    from
    @prisma-next/sql-runtime
    , and
    createTelemetryMiddleware
    from
    @prisma-next/middleware-telemetry
    . Tracked alongside the broader façade-completeness work in Linear
    TML-2526
    ; file additional gaps you hit via
    prisma-next-feedback
    .
  • Multi-database routing / read replicas. Prisma Next doesn't ship a built-in primary/replica router or shard-aware client. Workaround: configure separate
    db.ts
    instances per data store and call the right one in your application code. If you need first-class multi-database routing, file a feature request via the
    prisma-next-feedback
    skill.
  • Connection pooling as a first-class config field.
    poolOptions.connectionTimeoutMillis
    and
    poolOptions.idleTimeoutMillis
    are wired through, but the rest of
    pg.Pool
    's tuning surface (max connections,
    allowExitOnIdle
    , ssl options, …) is not exposed by name. Workaround: construct the
    pg.Pool
    yourself and pass it via
    pg:
    . If you need more pool fields surfaced on the façade, file a feature request via the
    prisma-next-feedback
    skill.
  • Query logger middleware as a built-in. Prisma Next doesn't ship a "log every query" middleware. Workaround: write a small custom middleware that wraps each operation and logs; or use
    createTelemetryMiddleware
    and log inside the
    onEvent
    callback. If you need a built-in query log, file a feature request via the
    prisma-next-feedback
    skill.
  • @prisma-next/postgres/middleware
    子路径
    。postgres外观层重新导出了运行时工厂(
    ./runtime
    )、配置(
    ./config
    )、契约构建器(
    ./contract-builder
    )、控制(
    ./control
    )、家族(
    ./family
    )、目标(
    ./target
    )和无服务器(
    ./serverless
    )——但不包括中间件。目前的解决方法:从
    @prisma-next/sql-runtime
    导入
    lints
    budgets
    ,从
    @prisma-next/middleware-telemetry
    导入
    createTelemetryMiddleware
    。请关注Linear中的
    TML-2526
    以了解外观层完整性的相关工作;如果遇到其他问题,请通过
    prisma-next-feedback
    提交。
  • 多数据库路由/只读副本。Prisma Next尚未内置主/副本路由器或分片感知客户端。解决方法:为每个数据存储配置单独的
    db.ts
    实例,并在应用代码中调用正确的实例。如果你需要官方支持多数据库路由,请通过
    prisma-next-feedback
    提交功能请求。
  • 连接池作为一等配置字段
    poolOptions.connectionTimeoutMillis
    poolOptions.idleTimeoutMillis
    已接入,但
    pg.Pool
    的其他调优接口(最大连接数、
    allowExitOnIdle
    、SSL选项等)未通过名称暴露。解决方法:自行构造
    pg.Pool
    并通过
    pg:
    传入。如果你需要外观层暴露更多连接池字段,请通过
    prisma-next-feedback
    提交功能请求。
  • 内置查询日志中间件。Prisma Next尚未提供“记录所有查询”的中间件。解决方法:编写一个小型自定义中间件包裹每个操作并记录;或使用
    createTelemetryMiddleware
    并在
    onEvent
    回调中记录。如果你需要内置查询日志,请通过
    prisma-next-feedback
    提交功能请求。

Reference Files

参考文件

This skill is intentionally body-only;
prisma-next init --help
, the
defineConfig
factory in
packages/3-extensions/postgres/src/config/define-config.ts
, the
postgres()
factory in
packages/3-extensions/postgres/src/runtime/postgres.ts
, and the middleware sources in
packages/2-sql/5-runtime/src/middleware/{lints,budgets}.ts
are the authoritative surfaces for option-level detail. When in doubt, read the source.
本技能仅包含主体内容;
prisma-next init --help
packages/3-extensions/postgres/src/config/define-config.ts
中的
defineConfig
工厂、
packages/3-extensions/postgres/src/runtime/postgres.ts
中的
postgres()
工厂,以及
packages/2-sql/5-runtime/src/middleware/{lints,budgets}.ts
中的中间件源代码是选项级细节的权威来源。如有疑问,请阅读源代码。

Checklist

检查清单

  • db.ts
    imports the runtime factory from
    @prisma-next/<target>/runtime
    and the
    Contract
    type from
    ./contract.d
    .
  • with { type: 'json' }
    on the contract JSON import.
  • <Contract>
    is the single type parameter on
    postgres<Contract>(...)
    (no second parameter).
  • DATABASE_URL
    lives in
    .env
    , not in
    prisma-next.config.ts
    .
  • Middleware ordered intentionally (telemetry outermost typically).
  • lints
    /
    budgets
    use the verified option keys (
    severities
    ,
    maxLatencyMs
    ,
    maxRows
    ,
    tableRows
    ).
  • Per-env divergence (if any) gated by
    NODE_ENV
    or similar.
  • Did NOT hardcode credentials in any committed file.
  • Did NOT confabulate a
    @prisma-next/postgres/middleware
    subpath, a
    @prisma-next/postgres-extension-audit
    package, or a second type parameter on
    postgres<...>
    .
  • Did NOT confabulate read-replica / multi-DB / extra pool config — pointed at What Prisma Next doesn't do yet and routed to
    prisma-next-feedback
    .
  • For build-system / dev-server prompts (Vite plugin, Next.js plugin, …) routed to
    prisma-next-build
    .
  • db.ts
    @prisma-next/<target>/runtime
    导入运行时工厂,并从
    ./contract.d
    导入
    Contract
    类型。
  • 契约JSON导入添加了
    with { type: 'json' }
  • postgres<Contract>(...)
    的唯一类型参数是
    <Contract>
    (无第二个参数)。
  • DATABASE_URL
    存储在
    .env
    中,而非
    prisma-next.config.ts
  • 中间件顺序经过合理安排(通常遥测放在最外层)。
  • lints
    /
    budgets
    使用已验证的选项键(
    severities
    maxLatencyMs
    maxRows
    tableRows
    )。
  • 多环境差异(如果有)通过
    NODE_ENV
    或类似变量控制。
  • 未在任何已提交文件中硬编码凭据。
  • 未错误使用
    @prisma-next/postgres/middleware
    子路径、
    @prisma-next/postgres-extension-audit
    包,或
    postgres<...>
    的第二个类型参数。
  • 未错误使用只读副本/多数据库/额外连接池配置——已指向Prisma Next目前不支持的功能并引导至
    prisma-next-feedback
  • 构建系统/开发服务器相关问题(Vite插件、Next.js插件等)已引导至
    prisma-next-build