Skip to content

pg 8.22.0 CJS entry can resolve pg-protocol as ESM under Cloudflare/Vite worker tests #3700

Description

@astanciu

Summary

When pg@8.22.0 is bundled by the @cloudflare/vitest-pool-workers / Vite worker-test pipeline in a pnpm workspace, the CommonJS entry can fail while loading pg-protocol from pg/lib/connection.js:

SyntaxError: Cannot use import statement outside a module
  at node_modules/.pnpm/pg@8.22.0/node_modules/pg/lib/connection.js?mf_vitest_no_cjs_esm_shim:5:30
  at node_modules/.pnpm/pg@8.22.0/node_modules/pg/lib/client.js?mf_vitest_no_cjs_esm_shim:10:20
  at node_modules/.pnpm/pg@8.22.0/node_modules/pg/lib/index.js

The failing line is the CommonJS package-name require:

const { parse, serialize } = require("pg-protocol")

In this environment, that dependency can be resolved/transformed as an ES module, then loaded from a CJS wrapper.

Environment

  • pg: 8.22.0
  • pg-protocol: 1.15.0
  • package manager: pnpm
  • test/bundler stack: @cloudflare/vitest-pool-workers + Vite + Miniflare/workerd
  • worker compatibility flag: nodejs_compat

Local workaround

We currently patch pg to force the CJS files to load the CJS dependency files directly from pnpm's sibling layout:

-const { parse, serialize } = require("pg-protocol")
+const { parse, serialize } = require("../../pg-protocol/dist/index.js")

-const Pool = require("pg-pool")
+const Pool = require("../../pg-pool/index.js")

-const parse = require("pg-connection-string").parse
+const parse = require("../../pg-connection-string/index.js").parse

That patch is not a good general solution because it depends on pnpm's installed package layout.

Ask

Would pg consider an upstream change that makes the CommonJS entry robust in bundler/worker environments, for example by ensuring the CJS entry always resolves CJS-compatible dependency entrypoints for pg-protocol, pg-pool, and pg-connection-string?

Happy to provide more details or test a candidate fix.

Activity

  1. brianc commented on Jun 26, 2026

    @brianc
    Owner
  2. astanciu commented on Jun 26, 2026

    @astanciu
    Author

    @brianc the version you mentioned got obfuscated as an email, mind sending it again? I've had to patch this since at least since 8.20

  3. brianc commented on Jun 26, 2026

    @brianc
    Owner

    Ah weird the email got eaten. I was wondering if v 8.19 or so also had the issue. I am fairly certain its due to the typescript compiler version change. Mildly frustrating how fragile that is. If you have a bit of time to bisect what version introduced the problem that would help me figure out an exact fix. Or a way to reproduce it locally. I have some esm/cjs import tests in the repository so not sure what didnt get caught.

  4. astanciu commented on Jun 26, 2026

    @astanciu
    Author

    Thanks. I tested this in a throwaway copy of our repo with the pg patch removed.

    Repro command:

    pnpm test apps/request-svc/tests/request-worker.test.ts

    Environment is @cloudflare/vitest-pool-workers + Vite + Miniflare/workerd, nodejs_compat, pnpm 11.9.0.

    Results:

    • pg@8.22.0 + pg-protocol@1.15.0: fails
    • pg@8.21.0 + pg-protocol@1.15.0: fails
    • pg@8.20.0 + pg-protocol@1.15.0: fails
    • pg@8.19.0: also fails in our workspace, though pnpm still resolves pg-protocol@1.15.0 because we use resolutionMode: highest

    I also forced older pg-protocol versions:

    • pg@8.22.0 + pg-protocol@1.14.0: fails
    • pg@8.22.0 + pg-protocol@1.12.0: fails

    So I don't think this is only the TS 6-built pg-protocol@1.15.0.

    The minimal fix I tested was changing only pg's CJS imports from:

    require("pg-protocol")

    to:

    require("pg-protocol/dist/index.js")

    in pg/lib/connection.js and pg/lib/index.js.

    That makes our worker test pass. pg-protocol already exports ./dist/*.js, so this avoids our current pnpm-relative workaround.

  5. astanciu commented on Jun 26, 2026

    @astanciu
    Author

    I reduced this to a small standalone repro. No database connection is needed; just importing pg inside the Cloudflare Vitest worker runtime is enough.

    Run:

    pnpm install
    pnpm test

    package.json

    {
      "name": "pg-worker-repro",
      "private": true,
      "type": "module",
      "packageManager": "pnpm@11.9.0",
      "scripts": {
        "test": "vitest run"
      },
      "dependencies": {
        "pg": "8.22.0"
      },
      "devDependencies": {
        "@cloudflare/vitest-pool-workers": "0.16.18",
        "typescript": "7.0.1-rc",
        "vitest": "4.1.9",
        "wrangler": "4.103.0"
      }
    }

    pnpm-workspace.yaml

    resolutionMode: highest
    
    allowBuilds:
      esbuild: true
      sharp: true
      workerd: true

    wrangler.jsonc

    {
      "$schema": "https://unpkg.com/wrangler@latest/config-schema.json",
      "name": "pg-worker-repro",
      "main": "src/worker.ts",
      "compatibility_date": "2026-02-24",
      "compatibility_flags": ["nodejs_compat"]
    }

    vitest.config.ts

    import { cloudflareTest } from "@cloudflare/vitest-pool-workers";
    import { defineConfig } from "vitest/config";
    
    export default defineConfig({
      plugins: [
        cloudflareTest({
          wrangler: {
            configPath: "./wrangler.jsonc",
          },
          miniflare: {
            compatibilityFlags: ["nodejs_compat"],
          },
        }),
      ],
      test: {
        include: ["src/**/*.test.ts"],
        server: {
          deps: {
            inline: ["pg", "pg-protocol"],
          },
        },
        ssr: {
          noExternal: ["pg", "pg-protocol"],
        },
      },
    });

    src/worker.ts

    export default {
      fetch() {
        return new Response("ok");
      },
    };

    src/pg.test.ts

    import { test, expect } from "vitest";
    import pg from "pg";
    
    test("pg imports in the Workers Vitest runtime", () => {
      expect(pg.Client).toBeTypeOf("function");
    });

    Failure:

    SyntaxError: Cannot use import statement outside a module
     ❯ node_modules/.pnpm/pg@8.22.0/node_modules/pg/lib/connection.js?mf_vitest_no_cjs_esm_shim:5:30
     ❯ node_modules/.pnpm/pg@8.22.0/node_modules/pg/lib/client.js?mf_vitest_no_cjs_esm_shim:10:20
     ❯ node_modules/.pnpm/pg@8.22.0/node_modules/pg/lib/index.js:3:16
    

    I also verified the reduced repro passes if pg changes these two CJS imports:

    // pg/lib/connection.js
    -const { parse, serialize } = require("pg-protocol")
    +const { parse, serialize } = require("pg-protocol/dist/index.js")
    
    // pg/lib/index.js
    -const { DatabaseError } = require("pg-protocol")
    +const { DatabaseError } = require("pg-protocol/dist/index.js")
  6. WolfieLeader commented on Oct 6, 2026

    @WolfieLeader

    I traced this to @cloudflare/vitest-plugin, not to pg. Under Vite 8, the plugin resolves every CommonJS require() with the import condition. It flags a require through custom['node-resolve'].isRequire, which Vite 8 ignores. That is cloudflare/workers-sdk#12984, with a draft fix in cloudflare/workers-sdk#13062.

    Evidence (pg 8.23.1, @cloudflare/vitest-plugin 1.3.6, vitest 4.1.11, Vite 8.3.3, no deps.optimizer workaround):

    Until the plugin is fixed: pre-bundle pg with test.deps.optimizer.ssr (enabled: true, include: ['pg']), keeping node:* built-ins external.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions