payloadcms / payloadcms/payload
Bumping @types/pg duplicates drizzle-orm in node_modules (yarn node-modules linker), breaking TS builds
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 44.8k
- Forks
- 4.2k
- Avg merge
- 2d 21h
- Merged PRs (30d)
- 53
Description
Describe the Bug
With Yarn 4's node-modules linker, bumping the devDependency @types/pg by even one patch/minor version (e.g. 8.20.0 → 8.21.0) causes Yarn to physically duplicate drizzle-orm on disk: one copy at node_modules/drizzle-orm, a second copy nested at node_modules/@payloadcms/db-postgres/node_modules/drizzle-orm. Both copies are the exact same drizzle-orm version.
Because the two copies are separate module instances, TypeScript treats their SQL<unknown> types as structurally incompatible (the private field shouldInlineParams gets two separate declarations), so any generated migration calling db.execute(sql\...`)(the pattern Payload itself generates viapayload migrate:create`) fails to type-check:
src/migrations/20260813_140000_tenants_tenant_id.ts(4,20): error TS2345: Argument of type 'SQL<unknown>' is not assignable to parameter of type 'string | SQLWrapper'.
Type 'SQL<unknown>' is not assignable to type 'SQLWrapper'.
The types returned by 'getSQL()' are incompatible between these types.
Type 'import(".../node_modules/@payloadcms/db-postgres/node_modules/drizzle-orm/sql/sql").SQL<unknown>' is not assignable to type 'import(".../node_modules/drizzle-orm/sql/sql").SQL<unknown>'.
Types have separate declarations of a private property 'shouldInlineParams'.
This broke a CI build for us after Renovate proposed a routine @types/pg bump (8.20.0 → 8.23.1) with no other related changes.
Root cause (as far as I can tell)
drizzle-orm declares @types/pg as an optional peer dependency. @payloadcms/db-postgres depends on drizzle-orm directly (not as a peer). Because @payloadcms/db-postgres itself has a peerDependencies entry on payload, Yarn's node-modules linker creates virtual, peer-scoped instances of @payloadcms/db-postgres (and transitively drizzle-orm) for different consumers in the graph. When the top-level project's @types/pg version changes, it shifts which of those virtual instances Yarn considers structurally identical enough to hoist/collapse into a single physical folder — below a certain @types/pg version they collapse to one copy, above it they don't, even though drizzle-orm itself never changes version.
I can reliably reproduce the flip with @types/pg alone (all other dependencies held constant):
@types/pg |
Physical drizzle-orm copies |
|---|---|
8.20.0 |
1 (single copy, hoisted) |
8.20.1–8.20.4 |
not tested |
8.21.0 |
2 (duplicated) |
8.23.1 |
2 (duplicated) |
yarn why drizzle-orm shows the duplicate virtual instances even in the "good" 8.20.0 case (they're just collapsed to one physical location by the linker):
├─ @payloadcms/db-postgres@npm:3.88.0
│ └─ drizzle-orm@npm:0.45.2 (via npm:0.45.2)
│
├─ @payloadcms/db-postgres@npm:3.88.0 [b8747]
│ └─ drizzle-orm@npm:0.45.2 [fa80e] (via npm:0.45.2 [fa80e])
│
├─ @payloadcms/drizzle@npm:3.88.0
│ └─ drizzle-orm@npm:0.45.2 (via npm:0.45.2)
│
└─ @payloadcms/drizzle@npm:3.88.0 [fa80e]
└─ drizzle-orm@npm:0.45.2 [00318] (via npm:0.45.2 [00318])
yarn dedupe --check does not flag this as dedupeable, since the two instances are considered structurally distinct by Yarn's peer resolution, not merely redundant version ranges.
Link to the code that reproduces this issue
Not shareable (private repo), but reproducible from scratch — see steps below.
Reproduction Steps
npx create-payload-app@latest(or any existing Payload 3 project) using the Postgres adapter, with Yarn 4 andnodeLinker: node-modulesin.yarnrc.yml.- Add
@types/pgas a devDependency pinned to8.20.0, runyarn install. - Confirm only one physical copy exists:
find node_modules -maxdepth 4 -type d -name drizzle-orm→ 1 result. - Bump
@types/pgto8.21.0(or8.23.1), runyarn installagain. - Re-run the same
findcommand → 2 results (node_modules/drizzle-ormandnode_modules/@payloadcms/db-postgres/node_modules/drizzle-orm). - Run
yarn payload migrate:create(or use any existing migration file that importssql/db.executefrom@payloadcms/db-postgres) andtsc/next build→ TS2345 as shown above.
Which area(s) are affected? (Select all that apply)
db-postgres
Environment Info
Binaries:
Node: v24.19.0
Yarn: 4.18.0 (nodeLinker: node-modules)
Relevant Packages:
payload: 3.88.0
@payloadcms/db-postgres: 3.88.0
@payloadcms/drizzle: 3.88.0
drizzle-orm: 0.45.2 (both copies)
@types/pg: 8.20.0 (works) / 8.21.0+ (breaks)
Operating System:
Platform: darwin
Arch: arm64
Workaround
Pin @types/pg to a version below the flip point (8.20.0 in our case) and avoid bumping it. Not a real fix since it just avoids re-triggering the linker's hoisting decision, and will presumably resurface with some other unrelated dependency bump that shifts the same peer-resolution graph.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with .yarnrc.yml and the dependency metadata for @payloadcms/db-postgres, @payloadcms/drizzle, and drizzle-orm; reproduce the @types/pg 8.20.0 to 8.21.0 change with the node-modules linker. Compare the physical copies and yarn why output, then run yarn payload migrate:create and tsc; done means the dependency bump no longer causes duplicate drizzle-orm instances or the reported TS2345 failure.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, postgresql, typescript
- Domain
- build-system, databases
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100