空雲 Blog

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

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

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 を使うことができるかもしれません。早速 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.

@prisma/adapter-pg-workerpg に Polyfill を追加して動作させるように作られたパッケージですが、nodejs_compat_v2 を有効にすると、こちらもエラーが出てしまいます。つまりこの機能が有効になると pg が完全に死にます。

動かす

ここで残念ながら動きませんでしたで終わらせるのはあまりにアホなので、pgnodejs_compat_v2 に対応させます。ということで、問題点を調べ修正を加え、パッチを作りました。pg の github 上のコードを見ると、一部修正されているのですが、まだ足りない部分がありました。

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 のバージョンアップでパッチが不要になることでしょう。また、compatibility_date の 2024-09-23 がやってきた後は、古いランタイムを使わないと nodejs_compatの状態で pg が死んでしまうので注意が必要です。