
Prisma Next 如何部署到 Cloudflare WorkersHyperdrive 配置、每请求 runtime 与本地验证【免费下载链接】ormNext-generation ORM for Node.js TypeScript | PostgreSQL, MySQL, MariaDB, SQL Server, SQLite, MongoDB and CockroachDB项目地址: https://gitcode.com/GitHub_Trending/pr/orm把 Prisma Next 的 Postgres 应用部署到 Cloudflare Workers 时核心问题是 Worker 的 per-request 生命周期不能把Runtime和它背后的pg.Client闭包缓存在 isolate 里跨fetch复用——isolate 闲置后会拿到失效连接并发fetch还会在单个共享pg.Client上竞争。Prisma Next 为此提供了每请求 facadepostgresServerless配合 Cloudflare Hyperdrive边缘 Postgres 连接池完成部署。本文以仓库中的 Serverless 部署指南 和可运行的完整示例 examples/prisma-8-cloudflare-worker 为准给出 Hyperdrive 配置、每请求 runtime 的代码形态以及本地验证和部署步骤。运行时路径是 Worker → Hyperdrive → 源 PostgresHyperdrive 持有源库凭据Worker 只从env.HYPERDRIVE.connectionString读连接串。控制面路径migrations不走 Hyperdrive由 Node 进程直连源库详见后文“部署”一节。前提条件示例工程的 README 列出的前置要求Node 满足仓库根package.json的engines.node24pnpm并在仓库根执行pnpm install安装 workspace 依赖dockerdocker composeDocker Desktop、OrbStack、Colima 或 Rancher Desktop 均可。本地 Postgres 源库跑在容器里示例用 docker-compose.yml 启动postgres:16端口映射为5433:5432刻意不用 5432避免与examples/prisma-8-demo的本地 Postgres 冲突数据目录走tmpfs以便快速重启。说明一点包名差异部署指南里的代码片段引用内部包internal/postgres/serverless而示例工程package.json依赖为prisma/orm-postgres版本8.0.0-rc.8及其源码引用的是公开包prisma/orm-postgres/serverless。两个包在仓库中都存在下文的代码以可直接运行的示例工程为准。配置 Hyperdrive先在 Cloudflare 上创建 Hyperdrive 配置--connection-string指向你的源 PostgresPrisma Postgres、AWS RDS、Neon、Supabase 等任何 Postgres 兼容源均可pnpm exec wrangler hyperdrive create my-hyperdrive \ --connection-stringpostgres://USER:PASSHOST:PORT/DBNAME其中USER、PASS、HOST、PORT、DBNAME替换为你自己的源库凭据。Wrangler 会打印一个 binding id把它填进wrangler.jsonc。示例工程的 wrangler.jsonc 当前提交的是一个占位 id{ $schema: node_modules/wrangler/config-schema.json, name: prisma-8-cloudflare-worker, main: src/worker.ts, compatibility_date: 2025-07-18, compatibility_flags: [nodejs_compat], hyperdrive: [ { binding: HYPERDRIVE, id: 00000000000000000000000000000000 } ] }compatibility_flags中的nodejs_compat是必需项pg驱动用到若干 Node 内建能力由 workerd 在该 flag 下提供 polyfill。提交到仓库的id是 0 填充的占位值直接部署会失败部署前必须替换为wrangler hyperdrive create输出的真实 id。生产环境注意部署前必读对真实 Hyperdrive默认 cursor 路径会挂起——pg-cursor的 extended-query 命名 portal 协议触发 Hyperdrive 的解析器 bug客户端在收到行后发送Close portal Sync时Hyperdrive 返回Protocol Error: Unexpected protocol code: CSQLSTATE58000且不再发送ReadyForQuery连接卡死Cloudflare 运行时在 30 秒后以 error 1101 杀掉请求。这影响所有读路径SQL DSL、ORM 的.all()/.first()、for awaitwithTransaction包起来也不解决问题。在上游修复落地前给postgresServerless({...})传入cursor: { disabled: true }下一节的模块级代码中演示。本地 miniflare 模拟器和 localhost Postgres 路径复现不了这个挂起所以本地测试开着 cursor 也能通过——该问题只在真实部署的 Hyperdrive 前出现。Worker 代码形态模块级 db 每请求 runtime完整文件是 src/worker.ts 和 src/prisma/db.ts。核心模式是“模块作用域构建一次、fetch内每次获取 runtime”。模块作用域每个 isolate 构建一次。只有静态 authoring surfacesql、context、stack、contract被闭包缓存——它们是契约的纯函数缓存安全runtime-bound 的表面每次fetch通过db.connect(...)获取// src/prisma/db.ts import { budgets, lints } from prisma/orm-postgres/family-runtime; import postgresServerless from prisma/orm-postgres/serverless; import type { Contract } from ./contract.d; import contractJson from ./contract.json with { type: json }; function createMiddleware() { return [ lints(), budgets({ maxRows: 10_000, defaultTableRows: 10_000, tableRows: { user: 10_000, post: 10_000 }, maxLatencyMs: 5_000, }), ]; } export const db postgresServerlessContract({ contractJson, middleware: createMiddleware(), // 源库位于 Cloudflare Hyperdrive 之后时必须加 // cursor: { disabled: true }, });contract.json/contract.d.ts由pnpm emit生成。fetch处理器内每请求获取 runtime。await using是AsyncDisposable语法fetch返回或抛错时自动执行runtime.close()终止底层pg.Client——没有闭包缓存也没有跨并发fetch的共享状态// src/worker.ts import { withTransaction } from prisma/orm-postgres/family-runtime; import { createOrmClient } from ./orm-client/client; import { db } from ./prisma/db; interface Env { HYPERDRIVE: { connectionString: string }; } export default { async fetch(request: Request, env: Env): PromiseResponse { const url new URL(request.url); if (url.pathname /health) { return Response.json({ ok: true }); } // Fresh runtime per fetch。AsyncDisposablefetch 返回或抛错时 // runtime.close() 自动运行结束底层 pg.Client。 await using runtime await db.connect({ url: env.HYPERDRIVE.connectionString }); // SQL DSL —— runtime.query 返回结果集 if (url.pathname /sql/users) { const rows await runtime.query( db.sql.public.user.select(id, email).limit(10).build(), ); return Response.json(rows); } // ORM —— 针对每请求 runtime 构建 if (url.pathname /orm/users) { const orm createOrmClient(runtime); const rows await orm.User.newestFirst().limit(10).all(); return Response.json(rows); } // 事务 —— withTransaction 接收每请求 runtime // BEGIN/COMMIT/ROLLBACK 发生在同一个底层 pg.Client 上 if (url.pathname /tx/commit) { const result await withTransaction(runtime, async (tx) { await tx.execute( db.sql.public.post.insert({ /* ... */ }).build(), ); await tx.execute( db.sql.public.user.update({ /* ... */ }).where(/* ... */).build(), ); return { committed: true }; }); return Response.json(result); } return Response.json({ ok: false, error: unknown route }, { status: 404 }); }, };示例工程实现的路由完整列表见示例 README路由表面说明GET /health—不碰 DB 的存活检查GET /sql/usersSQL DSLdb.sql.public.user.select(...).limit(?)GET /orm/usersORM clientUser.newestFirst().limit(?)GET /orm/postsORM clientPost.where({ userId }).orderBy(...).limit(?)GET /tx/commitwithTransactionINSERT post UPDATE user 原子提交GET /tx/rollbackwithTransaction在事务体内抛错验证 ROLLBACK 传播GET /cursor/largeCursor 流for await … break提前退出游标干净取消ORM client 工厂沿用 Node 侧的既有模式src/orm-client/client.ts唯一区别是在fetch内对着每请求runtime调用工厂而不是读闭包缓存的db.orm// src/orm-client/client.ts import type { Runtime } from prisma/orm-postgres/family-runtime; import { orm } from prisma/orm-postgres/orm-client; import type { ExecutionContext } from prisma/orm-postgres/relational-core/query-lane-context; import type { Contract } from ../prisma/contract.d; import { db } from ../prisma/db; import { PostCollection, UserCollection } from ./collections; const context db.context as ExecutionContextContract; export function createOrmClient(runtime: Runtime) { return orm({ runtime, context, collections: { User: UserCollection, Post: PostCollection, }, }).public; }自定义 collections、repository 和 ORM 扩展的用法与 Node 上相同。本地验证本地开发时Hyperdrive binding 需要一条本地连接串。Wrangler 从.env中的环境变量读取它绑定名为HYPERDRIVE# .envgitignore WRANGLER_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVEpostgres://postgres:postgres127.0.0.1:5433/prisma_next_cloudflare_worker这个变量要放在.env而不是.dev.vars.dev.vars存的是 worker 运行时 secret而WRANGLER_*_LOCAL_CONNECTION_STRING_*是 Wrangler 自己在构建本地 Hyperdrive binding 时消费的。WRANGLER_*前缀在新版 Wrangler 中正向CLOUDFLARE_*迁移截至wrangler4.87两种前缀都可用示例工程锁定wrangler4.119.0。.env.example 已预置 docker-compose 对应的 URL复制即可。一次性初始化在examples/prisma-8-cloudflare-worker/下pnpm emit # 生成 src/prisma/contract.{json,d.ts} cp .env.example .env # .env 已被 gitignore每次开发会话pnpm db:up # docker compose up -d --waitpostgres:16监听 5433 pnpm db:init # prisma db init → CREATE TABLE … pnpm seed # 插入 Alice Bob 50 条 posts启动 Worker 并手工验证pnpm dev # wrangler dev → http://localhost:8787 curl http://localhost:8787/health curl http://localhost:8787/orm/users?limit5/health返回{ ok: true }且不依赖数据库/orm/users能返回 JSON 行即说明每请求 runtime → 本地 Hyperdrive binding → 容器 Postgres 的链路通了。自动化验证是vitest-pool-workers集成测试它在workerd下启动 Worker把 Hyperdrive binding 指向本地 Docker Postgres覆盖 SQL DSL、ORM、事务和 cursor 提前退出路径。这是“模式是否端到端可用”的基准参照pnpm db:up # 确保容器在运行 pnpm test # vitest run --config vitest.config.ts在仓库根目录也可以这样跑pnpm test:examples --filter prisma-8-cloudflare-worker前提是容器已启动——这是本地开发前提不是 CI 前提。测试的globalSetuptest/global-setup.ts会读取.env、断言容器可达、幂等地应用 schema 并重新 seed。部署到 Cloudflare为真实 Cloudflare 账号创建 Hyperdrive 配置把打印出的 binding id 替换进wrangler.jsonc的占位idpnpm exec wrangler hyperdrive create my-hyperdrive --connection-stringpostgres://USER:PASSHOST:PORT/DBNAME # 将 wrangler.jsonc 中的 id 替换为打印出的 binding id源库在 Hyperdrive 之后时确认src/prisma/db.ts中已传cursor: { disabled: true }见“配置 Hyperdrive”一节的生产注意事项。执行部署。必须用pnpm run deploy而不是pnpm deploy——后者与 pnpm 内建deploy命令冲突会报ERR_PNPM_INVALID_DEPLOY_TARGETpnpm run deployMigrations 仍然留在 Node 侧对着源库连接串通常是DATABASE_URL运行不经过 Hyperdrive。原因迁移命令prisma db migrate、prisma db init是控制面操作跑在长生命周期的 Node 进程里CI、部署钩子、一次性脚本且 Hyperdrive 在边缘缓存查询结果迁移台账的陈旧读会导致重复应用或跳过的混乱。Cloudflare 官方建议就是控制面绕过 Hyperdrive本仓库遵循这一模式。本地示例里的pnpm db:init执行tsx scripts/setup-schema.ts即prisma db init就是这条路径的体现。可选的部署前检查pnpm deploy:dry-run即wrangler deploy --dry-run --outdir dist可以查看 bundle 体积示例工程的文档示例输出为Total Upload: 1289.96 KiB / gzip: 254.14 KiB约 254 KiB 压缩后体积。bundle 里包含pg、pg-protocol、pg-types、pg-cursor、pg-pool驱动静态引入但postgresServerless运行时不构建Pool、pg-cloudflarepg在navigator.userAgent Cloudflare-Workers时自动启用以及cloudflare/unenv-presetpolyfill。限制与常见问题与部署直接相关的已知限制来自部署指南与示例 README事务亲和性withTransaction(runtime, ...)体内的所有语句都跑在该 runtime 唯一的底层pg.Client上。在事务体内构造第二个await using runtime2 await db.connect(...)并把部分语句路由过去不会与外层事务保持一致——跨 runtime 边界的行为未定义。Isolate 内存Workers isolate 内存有界默认 128 MiBWorkers Unbound 更高。ORM 的findMany类操作会把结果集物化进 JS 数组limit(...)是硬上限需要流式时用 SQL DSL 的runtime.query(...)迭代器配合for await … break提前取消。Cursor 默认开启postgresServerless默认启用 cursor长生命周期postgres()facade 默认关闭非 Hyperdrive 源库保持默认即可Hyperdrive 源库必须cursor: { disabled: true }。bundle 体积如上所述约 254 KiB gzip文档示例值包含未使用的pg-pool/pg-cloudflare静态导入属预期行为而非正确性问题。pg.Pool不会被使用isolate 内没有连接池池化是生产中 Hyperdrive 的职责。示例 README 给出的排查清单pnpm db:up报Cannot connect to the Docker daemon先启动容器运行时Docker Desktop、OrbStack 等再重试。pnpm db:init报连接错误确认pnpm db:up成功且容器健康docker compose ps注意端口是5433 不是 5432与examples/prisma-8-demo的 Postgres.app 端口冲突会在这里暴露。wrangler dev能启动但/orm/users返回500 / connection error容器多半停了或忘了pnpm db:uppnpm db:reset可从干净状态恢复全部该命令会docker compose down -v删除容器与卷再重建数据本身在tmpfs上可放心执行。bundle 里出现pg-cloudflare但运行在 Node 上属预期pg通过lib/stream.js静态导入它运行时按userAgent选择 socket 实现。另外示例 README 记录了本地源库为什么不用prisma/devPGlite而用 Docker PostgresPGlite 的 TCP shim 与pg-cloudflare的 socket 层在 workerd 下交互会挂起所有碰库的路由在wrangler dev和vitest-pool-workers下都复现最终 M3 阶段改用 Docker Postgres 作为本地源库。下一步部署指南 docs/Serverless Deployment Guide.md 还给出了postgresServerless在其他 per-request 运行时AWS Lambda、Vercel Serverless/Edge、Deno Deploy、Bun edge的连接串来源对照以及为什么两个 facade/runtime长生命周期 vs/serverless每请求不对称的架构依据ADR 207。示例本身刻意保持最小 schema 和最小路由便于把自己的部署与它逐项对照冷启动基准文档示例值冷启动约 35 ms、warm p50 约 13 ms均在本地wrangler dev Docker Postgres 下测得供你部署后按真实 Hyperdrive 环境复测参考。【免费下载链接】ormNext-generation ORM for Node.js TypeScript | PostgreSQL, MySQL, MariaDB, SQL Server, SQLite, MongoDB and CockroachDB项目地址: https://gitcode.com/GitHub_Trending/pr/orm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考