空雲 Blog

Eye catchCloudflare の nodejs_compat_v2 を有効にして prisma から pg を使う

publication: 2024/09/12
update:2024/10/10

nodejs_compat_v2

nodejs_compat_v2 は Cloudflare Workers/Pages を扱う場合に、Node.js の機能と互換性を持たせることができます。nodejs_compat よりも対応する幅が増えました。compatibility_date の 2024-09-23 以降からは、v2 の機能がnodejs_compatに統合される予定です。

https://blog.cloudflare.com/more-npm-packages-on-cloudflare-workers-combining-polyfills-and-native-code/

ちなみに nodejs_compat は機能がランタイム側にあるため、利用してもデプロイ時のサイズに影響を与えないのですが、nodejs_compat_v2は デプロイ前にPolyfillが働くらしく、サイズ増加を覚悟する必要があります。

さっそく pg を使ってみる

Node.js の互換性が上がったということは、 Polyfill を追加で指定せずに pg を使うことができるかもしれません。早速 wrangler 上で Prisma を動作させてみました。

  • @prisma/adapter-pg + pg

1 [ERROR] TypeError: http://this.stream.once is not a function`

ビルドは通るものの、実行時にエラーになりました。

  • @prisma/adapter-pg-worker + @prisma/adapter-pg-worker

1X [ERROR] TypeError: Illegal invocation: function called with incorrect `this` reference.

こちらはCloudflare用のパッケージなので、本来動くはずなのですが、 nodejs_compat_v2 によって pg が誤動作するため、逆に動かなくなりました。

動かす

ここで残念ながら動きませんでしたで終わらせるのはあまりにアホなので、pgnodejs_compat_v2 に対応させます。ということで、問題点を調べ修正を加え、パッチを作りました。

https://www.npmjs.com/package/pg-compat

これをpgと一緒にインストールすると誤動作の原因のコードを自動修正します、これで pgnodejs_compat_v2 で動作するようになります。

サンプル

Remix + Prisma + PostgreSQL のサンプルを作りました。nodejs_compat_v2を有効にして動作させています。Prisma は @prisma/adapter-pg + pgの組み合わせで動作させています。これによって Node.js で動く開発環境と、Build 後の Wrangler 上の環境で、パッケージを切り替えずに動作させられます。

https://github.com/SoraKumo001/remix-nodejs_compat_v2

  • wrangler.toml

1compatibility_date = "2024-08-21"
2compatibility_flags = ["nodejs_compat_v2"]

  • app/routes/_index.tsx

1import { LoaderFunctionArgs } from "@remix-run/cloudflare";
2import { useLoaderData } from "@remix-run/react";
3// @prisma/xxx-worker is not used
4import pg from "pg";
5import { PrismaPg } from "@prisma/adapter-pg";
6import { PrismaClient } from "@prisma/client";
7
8export default function Index() {
9 const values = useLoaderData<string[]>();
10 return (
11 <div>
12 {values.map((v) => (
13 <div key={v}>{v}</div>
14 ))}
15 </div>
16 );
17}
18
19export async function loader({
20 context,
21}: LoaderFunctionArgs): Promise<string[]> {
22 const url = new URL(context.cloudflare.env.DATABASE_URL);
23 const schema = url.searchParams.get("schema") ?? undefined;
24 const pool = new pg.Pool({
25 connectionString: context.cloudflare.env.DATABASE_URL,
26 });
27 const adapter = new PrismaPg(pool, { schema });
28 const prisma = new PrismaClient({ adapter });
29 await prisma.test.create({ data: {} });
30 return prisma.test.findMany().then((r) => r.map(({ id }) => id));
31}

まとめ

とりあえずパッチを作って動くようにはしましたが、いずれ pg のバージョンアップでパッチが不要になることでしょう。