nestjs: EntityManager injection broken >5.1.2 with custom factory using PinoLogger and built via ncc

Describe the bug EntityManager (MongoEntityManager) dependency injection cannot be resolved when using @mikro-orm/nestjs >5.1.2 with .forRootAsync and PinoLogger. It works fine without injecting the logger into custom useFactory. Happens only when executing built code.

Stack trace

$ node ./dist/index.js
[Nest] 28664  - 07/18/2023, 11:02:59 AM     LOG [NestFactory] Starting Nest application...
[Nest] 28664  - 07/18/2023, 11:02:59 AM     LOG [InstanceLoader] AppModule dependencies initialized +97ms
[Nest] 28664  - 07/18/2023, 11:02:59 AM     LOG [InstanceLoader] MikroOrmModule dependencies initialized +0ms
[Nest] 28664  - 07/18/2023, 11:02:59 AM   ERROR [ExceptionHandler] Nest can't resolve dependencies of the MongoService (?). Please make sure that the argument MongoEntityManager at index [0] is available in the MongoModule context.

Potential solutions:
- Is MongoModule a valid NestJS module?
- If MongoEntityManager is a provider, is it part of the current MongoModule?
- If MongoEntityManager is exported from a separate @Module, is that module imported within MongoModule?
  @Module({
    imports: [ /* the Module containing MongoEntityManager */ ]
  })

Error: Nest can't resolve dependencies of the MongoService (?). Please make sure that the argument MongoEntityManager at index [0] is available in the MongoModule context.

Potential solutions:
- Is MongoModule a valid NestJS module?
- If MongoEntityManager is a provider, is it part of the current MongoModule?
- If MongoEntityManager is exported from a separate @Module, is that module imported within MongoModule?
  @Module({
    imports: [ /* the Module containing MongoEntityManager */ ]
  })

    at Injector.lookupComponentInParentModules (/Users/akieltyka/RubymineProjects/DeepCrawl/mikro-orm-nestjs-bug/webpack:/mikro-orm-nestjs-bug/node_modules/@nestjs/core/injector/injector.js:254:1)
    at Injector.resolveComponentInstance (/Users/akieltyka/RubymineProjects/DeepCrawl/mikro-orm-nestjs-bug/webpack:/mikro-orm-nestjs-bug/node_modules/@nestjs/core/injector/injector.js:207:1)
    at resolveParam (/Users/akieltyka/RubymineProjects/DeepCrawl/mikro-orm-nestjs-bug/webpack:/mikro-orm-nestjs-bug/node_modules/@nestjs/core/injector/injector.js:128:1)
    at async Promise.all (index 0)
    at Injector.resolveConstructorParams (/Users/akieltyka/RubymineProjects/DeepCrawl/mikro-orm-nestjs-bug/webpack:/mikro-orm-nestjs-bug/node_modules/@nestjs/core/injector/injector.js:143:1)
    at Injector.loadInstance (/Users/akieltyka/RubymineProjects/DeepCrawl/mikro-orm-nestjs-bug/webpack:/mikro-orm-nestjs-bug/node_modules/@nestjs/core/injector/injector.js:70:1)
    at Injector.loadProvider (/Users/akieltyka/RubymineProjects/DeepCrawl/mikro-orm-nestjs-bug/webpack:/mikro-orm-nestjs-bug/node_modules/@nestjs/core/injector/injector.js:97:1)
    at /Users/akieltyka/RubymineProjects/DeepCrawl/mikro-orm-nestjs-bug/webpack:/mikro-orm-nestjs-bug/node_modules/@nestjs/core/injector/instance-loader.js:56:1
    at async Promise.all (index 3)
    at InstanceLoader.createInstancesOfProviders (/Users/akieltyka/RubymineProjects/DeepCrawl/mikro-orm-nestjs-bug/webpack:/mikro-orm-nestjs-bug/node_modules/@nestjs/core/injector/instance-loader.js:55:1)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

To Reproduce package.json

{
  "name": "mikro-orm-nestjs-bug",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "compile": "tsc",
    "build": "ncc build --out dist --source-map ./reproduction.ts",
    "run:ts-node": "yarn ts-node ./reproduction.ts",
    "run:built": "node ./dist/index.js"
  },
  "resolutions": {
    "mongodb": "4.1.4"
  },
  "dependencies": {
    "@mikro-orm/core": "5.7.13",
    "@mikro-orm/mongodb": "5.7.13",
    "@mikro-orm/nestjs": "5.2.0",
    "@nestjs/common": "10.1.0",
    "@nestjs/config": "3.0.0",
    "@nestjs/core": "10.1.0",
    "nestjs-pino": "3.3.0",
    "pino-http": "8.3.3",
    "reflect-metadata": "0.1.13",
    "source-map-support": "0.5.21",
    "tslib": "2.6.0"
  },
  "devDependencies": {
    "@nestjs/cli": "10.1.4",
    "@nestjs/schematics": "10.0.1",
    "@nestjs/testing": "10.0.5",
    "@types/node": "18.16.19",
    "@vercel/ncc": "0.36.1",
    "ts-node": "10.9.1",
    "typescript": "5.1.6"
  }
}

tsconfig.json

{
  "compilerOptions": {
    "target": "es2021",
    "lib": ["es2021"],
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}

reproduction.ts

import "reflect-metadata";

import { BaseEntity, Entity, PrimaryKey, SerializedPrimaryKey } from "@mikro-orm/core";
import { EntityManager, ObjectId } from "@mikro-orm/mongodb";
import { MikroOrmModule } from "@mikro-orm/nestjs";
import { INestApplicationContext, Injectable, Module, Scope } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import { LoggerModule, PinoLogger } from "nestjs-pino";

@Entity({ collection: "entities" })
export class MongoEntity extends BaseEntity<MongoEntity, "id"> {
  @PrimaryKey()
  public _id!: ObjectId;

  @SerializedPrimaryKey()
  public id!: string;
}


@Injectable({ scope: Scope.TRANSIENT })
export class MongoService {
  private entityManager: EntityManager;

  constructor(entityManager: EntityManager) {
    this.entityManager = entityManager.fork();
  }
}

@Module({ providers: [MongoService] })
export class MongoModule {}

@Module({
  imports: [
    LoggerModule.forRoot(),
    MikroOrmModule.forRootAsync({
      inject: [PinoLogger],
      useFactory: (logger: PinoLogger) => {
        logger.setContext("MikroOrm");

        return {
          type: "mongo",
          clientUrl: "mongodb://127.0.0.1:27017/dbName", // run a local dockerized MongoDB v3 instance
          dbName: "dbName",
          entities: [MongoEntity],
          debug: ["query"], // Log all queries
          logger: message => logger.info(message),
        };
      },
    }),
    MongoModule,
  ],
})
class AppModule {}

let applicationContext: INestApplicationContext;

if (require.main === module) {
  (async () => {
    applicationContext = await NestFactory.createApplicationContext(AppModule);
    applicationContext = await applicationContext.init();
    await applicationContext.resolve(MongoService);
    await applicationContext.close();
    process.exit(0)
  })().catch(async error => {
    await applicationContext?.close();
    throw error;
  });
}
yarn install
yarn build
yarn run:built

Expected behavior I expect the EntityManager to get injected correctly and work as it was working before in version 5.1.2.

Additional context The error happens only when running a built .js file. It does not occur when running .ts file via ts-node. The mongodb dependency is locked to version 4.1.4 because of an old MongoDB v3 instance that needs to be connected to.

Versions

Dependency Version
node 18.16.1
typescript 5.1.6
mikro-orm 5.7.13
mongodb 4.1.4

About this issue

  • Original URL
  • State: open
  • Created a year ago
  • Comments: 17 (9 by maintainers)

Most upvoted comments

For example, if I make the whole MongoService in my reproduction have a scope but skip it in MikroOrmModule.forRootAsync, then I still get ValidationError when using the repository. As if injection ignored the fact that the whole service is scoped.

I am more than sure you need to use the scope option of the forRoot call, otherwise the services are registered as singletons, its not relevant that your MongoService (which is a leaf, not a dependency of those) is request scoped - you don’t import that into the ORM services, so they don’t have to he request scoped at all.

Oh ok, so the problem is in this package. I was suspecting some changes I did in v5.7.13 released a few days ago. Will try to look into this later this week.