berry icon indicating copy to clipboard operation
berry copied to clipboard

[Bug?]: Yarn PnP + monorepo breaks `instanceof` checks on errors from a shared package

Open Frederick888 opened this issue 6 months ago • 2 comments

Self-service

  • [ ] I'd be willing to implement a fix

Describe the bug

When a TypeORM error is thrown from a dependency package in a monorepo, checks such as err instanceof QueryFailedError in other packages fail if PnP is enabled.

To reproduce

~~(I encountered this issue using TypeORM, so the steps involve spinning up a MySQL server first. I'll see if I can find an easier way to reproduce it.)~~ Switched to SQLite, no longer needs a MySQL server.

  1. Clone https://github.com/Frederick888/playground/tree/typeorm-monorepo
  2. yarn install
  3. yarn build
  4. yarn bar:start

When using PnP, the output is (errIsQueryFailedError is true in the shared/ package but false in bar/):

{
  location: 'shared',
  errIsQueryFailedError: true,
  err: QueryFailedError: SQL driver error
      at ChildClass.doThrow (/home/frederick/Programming/Others/playground/packages/shared/dist/src/index.js:41:19)
      at main (/home/frederick/Programming/Others/playground/packages/bar/dist/index.js:8:38) {
    query: 'SELECT FOO',
    parameters: [ 1, 2, 3 ],
    driverError: Error: SQL driver error
        at ChildClass.doThrow (/home/frederick/Programming/Others/playground/packages/shared/dist/src/index.js:41:75)
        at main (/home/frederick/Programming/Others/playground/packages/bar/dist/index.js:8:38)
  }
}
{
  location: 'bar',
  errIsQueryFailedError: false,
  err: QueryFailedError: SQL driver error
      at ChildClass.doThrow (/home/frederick/Programming/Others/playground/packages/shared/dist/src/index.js:41:19)
      at main (/home/frederick/Programming/Others/playground/packages/bar/dist/index.js:8:38) {
    query: 'SELECT FOO',
    parameters: [ 1, 2, 3 ],
    driverError: Error: SQL driver error
        at ChildClass.doThrow (/home/frederick/Programming/Others/playground/packages/shared/dist/src/index.js:41:75)
        at main (/home/frederick/Programming/Others/playground/packages/bar/dist/index.js:8:38)
  }
}

When using nodeLinker: node-modules, the output becomes (both errIsQueryFailedError are true):

{
  location: 'shared',
  errIsQueryFailedError: true,
  err: QueryFailedError: SQL driver error
      at ChildClass.doThrow (/home/frederick/Programming/Others/playground/packages/shared/dist/src/index.js:41:19)
      at main (/home/frederick/Programming/Others/playground/packages/bar/dist/index.js:8:38) {
    query: 'SELECT FOO',
    parameters: [ 1, 2, 3 ],
    driverError: Error: SQL driver error
        at ChildClass.doThrow (/home/frederick/Programming/Others/playground/packages/shared/dist/src/index.js:41:75)
        at main (/home/frederick/Programming/Others/playground/packages/bar/dist/index.js:8:38)
  }
}
{
  location: 'bar',
  errIsQueryFailedError: true,
  err: QueryFailedError: SQL driver error
      at ChildClass.doThrow (/home/frederick/Programming/Others/playground/packages/shared/dist/src/index.js:41:19)
      at main (/home/frederick/Programming/Others/playground/packages/bar/dist/index.js:8:38) {
    query: 'SELECT FOO',
    parameters: [ 1, 2, 3 ],
    driverError: Error: SQL driver error
        at ChildClass.doThrow (/home/frederick/Programming/Others/playground/packages/shared/dist/src/index.js:41:75)
        at main (/home/frederick/Programming/Others/playground/packages/bar/dist/index.js:8:38)
  }
}

Environment

System:
    OS: Linux 6.12 Arch Linux
    CPU: (20) x64 12th Gen Intel(R) Core(TM) i9-12900H
  Binaries:
    Node: 24.2.0 - /tmp/xfs-318ae311/node
    Yarn: 4.9.2 - /tmp/xfs-318ae311/yarn
    npm: 11.4.2 - /usr/bin/npm
    bun: 1.2.16 - /usr/bin/bun

Additional context

Yarn version: 4.9.2

The issue can be fixed by re-exporting the types from shared/:

diff --git a/packages/bar/src/index.ts b/packages/bar/src/index.ts
index d04215d..61b5286 100644
--- a/packages/bar/src/index.ts
+++ b/packages/bar/src/index.ts
@@ -1,6 +1,5 @@
-import { AppDataSource, FooRepository } from "@playground/shared"
-import { QueryFailedError } from "typeorm"
+import { AppDataSource, FooRepository, QueryFailedError } from "@playground/shared"
 
 async function main() {
   await AppDataSource.initialize()
 
diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts
index aea52f0..8292ec6 100644
--- a/packages/shared/src/index.ts
+++ b/packages/shared/src/index.ts
@@ -37,5 +37,5 @@ export const FooRepository = AppDataSource.getRepository(Foo).extend({
             throw err
         }
     }
 })
-
+export { QueryFailedError }

Frederick888 avatar Jun 16 '25 11:06 Frederick888

Can't check right now, but I imagine they are missing a peer dependency, allowing packages to be duplicated between your dependencies and theirs.

arcanis avatar Jun 18 '25 06:06 arcanis

Can't check right now, but I imagine they are missing a peer dependency, allowing packages to be duplicated between your dependencies and theirs.

Yes, I can confirm moving typeorm to peerDependencies also fixed the issue (patch at the end).

I have to say this behaviour is quite error-prone. In my real project 'bar' is an API server and I just wanted to add another error handler rule on the QueryFailedError type. Originally typeorm was only a dependency of shared/. There are also other packages in my real monorepo that depends on shared/ but don't need the types from typeorm.

I guess logically it makes sense. But when I saw my unit tests passing (using new QueryFailedError(...)) but integration tests failing, it was quite an 'am I losing it' moment...

Is there any way to avoid duplicating identical dependencies?

$ y why -R typeorm
├─ @playground/bar@workspace:packages/bar
│  ├─ @playground/shared@workspace:packages/shared (via workspace:^)
│  └─ typeorm@npm:0.3.24 [c7eda] (via npm:^0.3.24 [c7eda])
│
└─ @playground/shared@workspace:packages/shared
   └─ typeorm@npm:0.3.24 [da905] (via npm:^0.3.24 [da905])
diff --git a/packages/bar/package.json b/packages/bar/package.json
index db7b6f0..5ca130a 100644
--- a/packages/bar/package.json
+++ b/packages/bar/package.json
@@ -3,8 +3,9 @@
   "version": "1.0.0",
   "dependencies": {
     "@playground/shared": "workspace:^",
     "reflect-metadata": "^0.2.2",
+    "sqlite3": "^5.1.7",
     "typeorm": "^0.3.24"
   },
   "main": "dist/index.js",
   "private": true,
diff --git a/packages/shared/package.json b/packages/shared/package.json
index ca167c4..d83872d 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -14,9 +14,11 @@
     "tsc-alias": "^1.8.10"
   },
   "dependencies": {
     "@types/node": "^24.0.3",
-    "reflect-metadata": "^0.2.2",
+    "reflect-metadata": "^0.2.2"
+  },
+  "peerDependencies": {
     "sqlite3": "^5.1.7",
     "typeorm": "^0.3.24"
   }
 }
diff --git a/yarn.lock b/yarn.lock
index c009542..46fb300 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -107,8 +107,9 @@ __metadata:
   resolution: "@playground/bar@workspace:packages/bar"
   dependencies:
     "@playground/shared": "workspace:^"
     reflect-metadata: "npm:^0.2.2"
+    sqlite3: "npm:^5.1.7"
     tsc-alias: "npm:^1.8.10"
     typeorm: "npm:^0.3.24"
   languageName: unknown
   linkType: soft
@@ -118,11 +119,12 @@ __metadata:
   resolution: "@playground/shared@workspace:packages/shared"
   dependencies:
     "@types/node": "npm:^24.0.3"
     reflect-metadata: "npm:^0.2.2"
-    sqlite3: "npm:^5.1.7"
     tsc-alias: "npm:^1.8.10"
-    typeorm: "npm:^0.3.24"
+  peerDependencies:
+    sqlite3: ^5.1.7
+    typeorm: ^0.3.24
   languageName: unknown
   linkType: soft
 
 "@sqltools/formatter@npm:^1.2.5":

Frederick888 avatar Jun 18 '25 07:06 Frederick888

Is there any way to avoid duplicating identical dependencies?

peerDependency is the way to do that. It is the only thing that tell package managers to enforce using the same package instance in two places in the dependency tree.

Regular dependency never enforces package identity. Any observed deduplication is not guaranteed and should not be relied upon

clemyan avatar Oct 22 '25 15:10 clemyan

Thanks for the explanation 👍

Frederick888 avatar Oct 23 '25 06:10 Frederick888