Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for nestjs@8.0.0 #29

Merged
merged 2 commits into from
Aug 19, 2021
Merged

Conversation

StrikeForceZero
Copy link
Contributor

@StrikeForceZero StrikeForceZero commented Jul 13, 2021

resolves: #27 (maybe)

As mentioned in the issue nestjs@^8.0.0 uses class instances instead of strings.
Took the suggestion to remove the driver specific imports in favor of the one from knex

Tested minimally but might be all that's needed?

The 2 occurrences I identified as problematic are highlighted in this MR.

I actually don't really intend on this being pulled in. I think passing in the respective EntityManager sub class to forRoot would be more appropriate and a lot cleaner, but this might help give a starting point.

src/mikro-orm-core.module.ts Outdated Show resolved Hide resolved
src/mikro-orm-core.module.ts Outdated Show resolved Hide resolved
src/mikro-orm-core.module.ts Outdated Show resolved Hide resolved
@B4nan
Copy link
Member

B4nan commented Jul 19, 2021

I actually don't really intend on this being pulled in.

Started looking into the code before I read the OP :] Ok understood, thanks for the insights. If we can make this work without BCs, then it would be great even if it would be quirky solution. We can have something cleaner in v5.

@StrikeForceZero
Copy link
Contributor Author

@B4nan took the liberty of trying out what you suggested

src/mikro-orm-core.module.ts Outdated Show resolved Hide resolved
TReturnValue,
>(moduleName: TModuleName, returnFn: (module: EntityManagerExportObj<TModuleName>) => AsyncOrSync<TReturnValue>): Promise<[Awaited<TReturnValue>] | []> {
try {
const module = await import(moduleName);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do i read it correctly that once we move to native ESM, we cant use forRoot and it will have to be forRootAsync? is there any real difference apart from the name? i mean it feels like just our own logic that offers two ways, but for nest it is the same, right? so once that happens, we basically drop forRoot and rename forRootAsync to just forRoot

Copy link
Contributor Author

@StrikeForceZero StrikeForceZero Jul 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that is correct.

In fact, forRootAsync wasn't marked as async prior to this PR, so the signature was the same by not returning a promise.

Copy link
Contributor Author

@StrikeForceZero StrikeForceZero Jul 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strange. Testing forRootAsync might have uncovered some code paths that do not have tests on them and are causing errors in my project. I'm going to do some investigation and report back.

Copy link
Contributor Author

@StrikeForceZero StrikeForceZero Jul 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh maybe I'm being naive, forRootAsync doesn't have to be async; it can return arrays of Promises for exports and providers that resolve as DynamicModules and Providers respectively.

Maybe its worth taking the time to not await all those promises so they are being loaded concurrently instead of sequentially.

Copy link
Contributor Author

@StrikeForceZero StrikeForceZero Jul 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wrong, providers do not appear to support Promises, only exports.
So what I have is probably the best I can come up with for now.

Edit: and any performance impact from a quick inspection appears to be negligible if any awaiting sequentially. Not to mention that users probably wont have both modules available.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forRootAsync usually implies is that it will be able to consume async factory providers that provide config for the module, not that the method will be async.

If you want the dynamic module to be async itself, you should be able to mark both forRoot and forRootAsync as async methods and then await the whenModuleAvailableAsync method. This would also make the whenModuleAvailable method useless.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@B4nan was there anything actionable here for me prior to getting this accepted? or did we want to go a different route?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was hoping to get rid of the require statement, not having two ways for the same thing. as @jtsshieh noted, it should be possible to just define the method as async and simplify the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah ok, thanks for clearing that up for me. 53c0a4d should reflect that now

@ByronDDelpinal
Copy link

Hey all, I'd love to help move this effort along in any way I can. I have a project that I want to use NestJS and Kafka (requires NestJS@8) with MikroORM (requires NestJS@7 currently). Can I help test or advise at all?

@B4nan
Copy link
Member

B4nan commented Aug 4, 2021

@ByronDDelpinal it would help a lot if you could verify that the changes done in the PR are backwards compatible, so we can call this semver-minor and not a BC.

@ghost
Copy link

ghost commented Aug 5, 2021

Hey, I too am waiting on NestJS v8 support for a project of mine. Is there anything I could do to assist?

@pragmaticivan
Copy link

Is there any way I could provide help here as well?

@nikkonrom
Copy link

nikkonrom commented Aug 8, 2021

@B4nan Hey, any updates on it? Let us know if any help needed

@tolgap
Copy link

tolgap commented Aug 11, 2021

Should it be possible for forRootAsync to work without having to be async itself? For creating async providers, i think you should be able to create an injectable that implements OnModuleInit and does its async work in onModuleInit():

@Injectable()
export class EntityManagerService implements OnModuleInit {
  constructor(private moduleRef: ModuleRef) {}

  async onModuleInit() {
    // const type = await figureOutWhichEntityManagerTypeToUse()

    // if sql entity manager is available, you register it into the module as:
    await this.moduleRef.create<SqlEntityManager>(sqlEntityManager)
    // or if mongo is available
    await this.moduleRef.create<MongoEntityManager>(mongoEntityManager)
  }
}

And register this injectable service as a provider in the MikroOrmCoreModule.

@StrikeForceZero do you think this would work? I might be able to check if this would work myself in a few days.

EDIT: I've tried this, and wasn't able to get it to work. moduleRef.create is something different than what I thought.

@B4nan
Copy link
Member

B4nan commented Aug 19, 2021

I just shipped the contents of this PR to the next dist tag, as 4.3.0-dev.0, so please test it in your projects guys, will try the example apps now.

edit: Looks like this it not working for custom repositories without InjectRepository decorator (it is optional if the repository is named like the entity is plus the suffix). In v7 it was resolved as we register the string token, I guess in v8 we would have to use the class reference instead?

edit2: found a hack to get around it by checking the global metadata storage, so will work only for decorators, still better than nothing

Fixes usage of custom repositories without `@InjectRepository` decorator.
@B4nan B4nan changed the title add support for nestjs@8.0.0 feat: add support for nestjs@8.0.0 Aug 19, 2021
@B4nan B4nan merged commit e512067 into mikro-orm:master Aug 19, 2021
@B4nan
Copy link
Member

B4nan commented Aug 20, 2021

v4.3 stable is out

@msheakoski
Copy link

msheakoski commented Aug 20, 2021

I am receiving this error after upgrading to v4.3 stable + Nest 8 and not exactly sure what to do to resolve it. v4.3 + Nest 7 still seems to work fine though:

[Nest] 15299  - 08/20/2021, 11:43:01 AM   ERROR [ExceptionHandler] Nest can't resolve dependencies of the MikroOrmCoreModule (Symbol(mikro-orm-module-options), ?). Please make sure that the argument ModuleRef at index [1] is available in the MikroOrmCoreModule context.

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

Error: Nest can't resolve dependencies of the MikroOrmCoreModule (Symbol(mikro-orm-module-options), ?). Please make sure that the argument ModuleRef at index [1] is available in the MikroOrmCoreModule context.

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

    at Injector.lookupComponentInParentModules (backend/node_modules/@nestjs/core/injector/injector.js:193:19)
    at Injector.resolveComponentInstance (backend/node_modules/@nestjs/core/injector/injector.js:149:33)
    at resolveParam (backend/node_modules/@nestjs/core/injector/injector.js:103:38)
    at async Promise.all (index 1)
    at Injector.resolveConstructorParams (backend/node_modules/@nestjs/core/injector/injector.js:118:27)
    at Injector.loadInstance (backend/node_modules/@nestjs/core/injector/injector.js:47:9)
    at Injector.loadProvider (backend/node_modules/@nestjs/core/injector/injector.js:69:9)
    at async Promise.all (index 0)
    at InstanceLoader.createInstancesOfProviders (backend/node_modules/@nestjs/core/injector/instance-loader.js:44:9)
    at backend/node_modules/@nestjs/core/injector/instance-loader.js:29:13

src/app.module.ts contains only the MikroORM module with empty options since it reads from mikro-orm.config.ts:

import { Module } from "@nestjs/common"
import { MikroOrmModule } from "@mikro-orm/nestjs"

@Module({
  imports: [MikroOrmModule.forRoot()],
})
export class AppModule {}

@B4nan
Copy link
Member

B4nan commented Aug 20, 2021

This is exactly how the realworld example does it too, and it works just fine in there. My guess is you have some nest packages still on v7, all of them need to be upgraded.

If you want more help, prepare full reproduction. Or check out the realworld example and try to compare what you are doing differently.

https://github.com/mikro-orm/nestjs-realworld-example-app
https://github.com/mikro-orm/nestjs-realworld-example-app/blob/master/src/app.module.ts#L15

@msheakoski
Copy link

msheakoski commented Aug 20, 2021

@B4nan it seems to be a yarn workspaces issue with hoisting. I made the following change to package.json and it seems to be working now. It might be a good note to mention in the README because it is not immediately obvious from Nest's error output:

Add this to monorepo-root/subproject/package.json:

"private": true,
"workspaces": {
  "nohoist": [
    "@mikro-orm/nestjs"
  ]
},

@B4nan
Copy link
Member

B4nan commented Aug 22, 2021

That sounds more like a workaround for a misconfigured monorepo (e.g. some undeclared peer deps), could you provide a repro for that so I can review it first? I don't want to add hints to the docs without proper understanding of the problem.

Ideally we should have an example app for monorepos too.

@qqil
Copy link

qqil commented Aug 25, 2021

app.module.ts:


@Module({
  imports: [
    MikroOrmModule.forRoot({
      entities: ['./dist/entities/*.entity.ts'],
      entitiesTs: ['./src/entities/*.entity.ts'],
      host: 'postgres',
      port: 5432,
      user: '',
      password: '',
      dbName: '',
      type: 'postgresql',
      tsNode: true,
      metadataProvider: TsMorphMetadataProvider,
      autoLoadEntities: true,
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Output when i run 'npm start:dev':

[8:36:32 PM] File change detected. Starting incremental compilation...
[8:36:32 PM] Found 0 errors. Watching for file changes.
[Nest] 798503  - 08/25/2021, 8:36:33 PM     LOG [NestFactory] Starting Nest application...
[Nest] 798503  - 08/25/2021, 8:36:33 PM     LOG [InstanceLoader] MikroOrmModule dependencies initialized +105ms
[Nest] 798503  - 08/25/2021, 8:36:33 PM     LOG [InstanceLoader] AppModule dependencies initialized +1ms
[Nest] 798503  - 08/25/2021, 8:36:33 PM   ERROR [ExceptionHandler] Cannot use import statement outside a module
/home/qqil/Projects/nestjs-testing/src/entities/user.entity.ts:1
import { Entity, PrimaryKey, Property } from '@mikro-orm/core';
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at wrapSafe (internal/modules/cjs/loader.js:979:16)
    at Module._compile (internal/modules/cjs/loader.js:1027:27)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Module.load (internal/modules/cjs/loader.js:928:32)
    at Function.Module._load (internal/modules/cjs/loader.js:769:14)
    at Module.require (internal/modules/cjs/loader.js:952:19)
    at require (internal/modules/cjs/helpers.js:88:18)
    at MetadataDiscovery.getEntityClassOrSchema (/home/qqil/Projects/nestjs-testing/node_modules/@mikro-orm/core/metadata/MetadataDiscovery.js:689:25)
    at MetadataDiscovery.discoverDirectories (/home/qqil/Projects/nestjs-testing/node_modules/@mikro-orm/core/metadata/MetadataDiscovery.js:104:34)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)

Package versions:


  "dependencies": {
    "@mikro-orm/core": "^4.5.9",
    "@mikro-orm/nestjs": "^4.3.0",
    "@mikro-orm/postgresql": "^4.5.9",
    "@mikro-orm/reflection": "^4.5.9",
    "@nestjs/common": "^8.0.6",
    "@nestjs/core": "^8.0.6",
    "@nestjs/platform-express": "^8.0.6",
    "class-validator": "^0.13.1",
    "reflect-metadata": "^0.1.13",
    "rimraf": "^3.0.2",
    "rxjs": "^7.3.0"
  },
  "devDependencies": {
    "@nestjs/cli": "^8.1.1",
    "@nestjs/schematics": "^8.0.2",
    "@nestjs/testing": "^8.0.6",
    "@types/express": "^4.17.13",
    "@types/jest": "^27.0.1",
    "@types/node": "^16.7.1",
    "@types/supertest": "^2.0.11",
    "@typescript-eslint/eslint-plugin": "^4.29.2",
    "@typescript-eslint/parser": "^4.29.2",
    "eslint": "^7.32.0",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-prettier": "^3.4.1",
    "jest": "^27.0.6",
    "prettier": "^2.3.2",
    "supertest": "^6.1.6",
    "ts-jest": "^27.0.5",
    "ts-loader": "^9.2.5",
    "ts-node": "^10.2.1",
    "tsconfig-paths": "^3.10.1",
    "typescript": "^4.3.5"
  },

I'm missing something?

@EduVencovsky
Copy link

@B4nan it seems to be a yarn workspaces issue with hoisting. I made the following change to package.json and it seems to be working now. It might be a good note to mention in the README because it is not immediately obvious from Nest's error output:

Add this to monorepo-root/subproject/package.json:

"private": true,
"workspaces": {
  "nohoist": [
    "@mikro-orm/nestjs"
  ]
},

I'm also using monorepo and for the strangest reason, this worked.

@msheakoski do you have any idea why? Maybe it has to do with the location of the node_modules and a fix could be made to it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add Nest 8 support
10 participants