nestjs / nest

A progressive Node.js framework for building efficient, scalable, and enterprise-grade server-side applications with TypeScript/JavaScript 🚀
https://nestjs.com
MIT License
67.69k stars 7.63k forks source link

Hooks in global module which imported more than twice might called multiple times #13492

Closed 5d-jh closed 6 months ago

5d-jh commented 6 months ago

Is there an existing issue for this?

Current behavior

If Dynamic module once imported as intended(a), and just imported as just class token(b), dynamic module's lifecycle hook called twice. Need to deduplicate modules by metatype for this case.

class ModuleA implements OnApplicationShutdown {
  static forRoot() {
    global: true,
    module: ModuleA
  }

  onApplicationShutdown() {
    console.log('shutting down....')
  }
}

class ModuleB {
  static forRoot() {
    global: true,
    module: ModuleB,
    imports: [ModuleA], // <-- (b)
  }
}

class ModuleC {
  static register() {
    global: true,
    module: ModuleC
    imports: [ModuleA.forRoot()] // <-- (a)
  }
}

@Module({
  imports: [ModuleB.forRoot(), ModuleC.forRoot()]
})
class AppModule {}

const app = NestFactory.create(AppModule)
await app.init();
await app.close();

In console

shutting down....
shutting down....

Minimum reproduction code

https://codesandbox.io/p/devbox/funny-violet-pf2kz5?file=%2Fsrc%2Fapp.controller.ts%3A9%2C23&layout=%257B%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522rootPanelGroup%2522%253A%257B%2522direction%2522%253A%2522horizontal%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522id%2522%253A%2522ROOT_LAYOUT%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522clvdkti5f00073b6jiep2ma48%2522%252C%2522sizes%2522%253A%255B70%252C30%255D%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522EDITOR%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522id%2522%253A%2522clvdkti5f00023b6jphhnw11u%2522%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522SHELLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522id%2522%253A%2522clvdkti5f00043b6jm6nkm1b4%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522DEVTOOLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522id%2522%253A%2522clvdkti5f00063b6jlknz8215%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%252C%2522sizes%2522%253A%255B50%252C50%255D%257D%252C%2522tabbedPanels%2522%253A%257B%2522clvdkti5f00023b6jphhnw11u%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clvdkti5f00013b6j9nljpeuu%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252FREADME.md%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%252C%257B%2522id%2522%253A%2522clvdl5dne001s3b6j6ocdseo4%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522initialSelections%2522%253A%255B%257B%2522startLineNumber%2522%253A11%252C%2522startColumn%2522%253A1%252C%2522endLineNumber%2522%253A11%252C%2522endColumn%2522%253A1%257D%255D%252C%2522filepath%2522%253A%2522%252Fsrc%252Fapp.module.ts%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%252C%257B%2522id%2522%253A%2522clvdl9rmy00023b6jk10bypf8%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522initialSelections%2522%253A%255B%257B%2522startLineNumber%2522%253A9%252C%2522startColumn%2522%253A23%252C%2522endLineNumber%2522%253A9%252C%2522endColumn%2522%253A23%257D%255D%252C%2522filepath%2522%253A%2522%252Fsrc%252Fapp.controller.ts%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%255D%252C%2522id%2522%253A%2522clvdkti5f00023b6jphhnw11u%2522%252C%2522activeTabId%2522%253A%2522clvdl9rmy00023b6jk10bypf8%2522%257D%252C%2522clvdkti5f00063b6jlknz8215%2522%253A%257B%2522id%2522%253A%2522clvdkti5f00063b6jlknz8215%2522%252C%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clvdkti5f00053b6jtp4iw65u%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522TASK_PORT%2522%252C%2522taskId%2522%253A%2522start%2522%252C%2522port%2522%253A3000%252C%2522path%2522%253A%2522%252F%2522%257D%255D%252C%2522activeTabId%2522%253A%2522clvdkti5f00053b6jtp4iw65u%2522%257D%252C%2522clvdkti5f00043b6jm6nkm1b4%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clvdkti5f00033b6jtxoe13ki%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522TASK_LOG%2522%252C%2522taskId%2522%253A%2522start%2522%257D%255D%252C%2522id%2522%253A%2522clvdkti5f00043b6jm6nkm1b4%2522%252C%2522activeTabId%2522%253A%2522clvdkti5f00033b6jtxoe13ki%2522%257D%257D%252C%2522showDevtools%2522%253Atrue%252C%2522showShells%2522%253Atrue%252C%2522showSidebar%2522%253Atrue%252C%2522sidebarPanelSize%2522%253A15%257D

Steps to reproduce

No response

Expected behavior

In console

shutting down....

Package

Other package

No response

NestJS version

10.3.2

Packages versions

{
  "name": "nest-typescript-starter",
  "private": true,
  "version": "1.0.0",
  "description": "Nest TypeScript starter repository",
  "license": "MIT",
  "scripts": {
    "build": "nest build",
    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
    "start": "nest start",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "node dist/main",
    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/jest/bin/jest --runInBand",
    "test:e2e": "jest --config ./test/jest-e2e.json"
  },
  "dependencies": {
    "@nestjs/common": "^10.3.2",
    "@nestjs/core": "^10.3.2",
    "@nestjs/platform-express": "^10.3.2",
    "reflect-metadata": "^0.2.1",
    "rxjs": "^7.8.1"
  },
  "devDependencies": {
    "@nestjs/cli": "^10.3.1",
    "@nestjs/schematics": "^10.1.0",
    "@nestjs/testing": "^10.3.2",
    "@swc/cli": "^0.3.9",
    "@swc/core": "^1.4.0",
    "@types/express": "^4.17.21",
    "@types/jest": "^29.5.12",
    "@types/node": "^20.11.16",
    "@types/supertest": "^6.0.2",
    "@typescript-eslint/eslint-plugin": "^6.21.0",
    "@typescript-eslint/parser": "^6.21.0",
    "eslint": "^8.56.0",
    "eslint-config-prettier": "^9.1.0",
    "eslint-plugin-prettier": "^5.1.3",
    "jest": "^29.7.0",
    "prettier": "^3.2.5",
    "source-map-support": "^0.5.21",
    "supertest": "^6.3.4",
    "ts-jest": "^29.1.2",
    "ts-loader": "^9.5.1",
    "ts-node": "^10.9.2",
    "tsconfig-paths": "^4.2.0",
    "typescript": "^5.3.3"
  },
  "jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      "ts"
    ],
    "rootDir": "src",
    "testRegex": ".*\\.spec\\.ts$",
    "transform": {
      "^.+\\.(t|j)s$": "ts-jest"
    },
    "collectCoverageFrom": [
      "**/*.(t|j)s"
    ],
    "coverageDirectory": "../coverage",
    "testEnvironment": "node"
  }
}

Node.js version

20.9.0

In which operating systems have you tested?

Other

No response

micalevisk commented 6 months ago

technically, ModuleA and ModuleA.forRoot() aren't the same thing so you'll have two different instances of ModuleA class in this case, which is why the hooks were called twice. I might be wrong but I believe that this is the desired behavior

kamilmysliwiec commented 6 months ago

Thank you for taking the time to submit your report! From the looks of it, this could be better discussed on our Discord. If you haven't already, please join here and send a new post in the #⁠ 🐈 nestjs-help forum. Make sure to include a link to this issue, so you don't need to write it all again. We have a large community of helpful members, who will assist you in getting this to work.