Papooch / nestjs-cls

A continuation-local storage (async context) module compatible with NestJS's dependency injection.
https://papooch.github.io/nestjs-cls/
MIT License
393 stars 23 forks source link

Error using nestjs-cls in an external module #51

Closed alessandrosangalli closed 1 year ago

alessandrosangalli commented 1 year ago

I have a external module (in a private repository) using nestjs-cls

MyModuleUsingNestjsCls imports
ClsModule.forRoot({ global: true, middleware: { mount: true }, }) provide: MyServiceUsingClsService exports: MyServiceUsingClsService

This module has a provider that uses ClsService: class MyService constructor(private readonly clsService: ClsService) {}

This app works fine when i start this module, the cls service also works.

If i import MyModuleUsingNestjsCls in another nestjs app like this: MyAnotherAppModule imports MyModuleUsingNestjsCls (from an repository like import { MyModuleUsingNestjsCls } 'my-private-lib')

I have the following error: Nest can't resolve dependencies of the ClsModule (?, ModuleRef). Please make sure that the argument HttpAdapterHost at index [0] is available in the ClsModule context.

Any idea why this happens?

Papooch commented 1 year ago

First thing that comes to mind, which is pretty common, is that the versions of @nestjs/* dependencies of the published library do not match the ones in the app, therefore looking for a different HttpAdapterHost. Make sure that all @nestjs/* dependencies in your library are peerDependencies with somewhat loose ranges, so it stays compatible when your app's dependencies update.

If you want me to investigate further, I'll need some reproducible example that I could look at.

alessandrosangalli commented 1 year ago

I create two repositories to reproduce.

A lib using nestjs-cls: https://github.com/alessandrosangalli/lib-with-cls And an app using the 'lib-with-cls': https://github.com/alessandrosangalli/app-using-lib

You can clone both in the same folder like /src/lib-with-cls and /src/app-using-lib because the app is pointing to local lib in package json "lib-with-cls": "file:../lib-with-cls".

To reproduce: cd lib-with-cls -> npm i -> npm run build cd app-using-lib -> npm i -> npm run start dev

You should see Error: Nest can't resolve dependencies of the ClsModule (?, ModuleRef). Please make sure that the argument HttpAdapterHost at index [0] is available in the ClsModule context.

Both repositories are created from the same NestJS version (9.1.4). This just happens when nestjs-cls is in a lib, when i use directly in my app works fine.

Papooch commented 1 year ago

I just looked at the code and indeed, you have hard dependencies on @nestjs/* and other things in the library code. This will cause two versions of the libs being installed (especially using the file:.. protocol, because npm can't dedupe the dependencies in that case), which breaks the instanceof check that NestJS relies on. All these should be peerDependencies: image

Although I'm not entirely sure if you can use the file:.. protocol with NestJS projects at all..

Papooch commented 1 year ago

Yep, the issue is definitely with duplicate package install. Setting the dependencies to peerDependencies does not help in this case (but should help for a published package). Manually moving the built files _without nodemodules inside the app-using-lib and linking it there causes the app to start successfully: image The underlying reason is that using the link:.. protocol causes the library code to load dependencies from its own node_modules, so even when the version of @nestjs/common is the same, the loaded code is different, which in turn breaks the dependency injection. When you publish the package and install it "normally", the problem should be gone.

This is a known problem of NPM (you can read more about it here https://github.com/npm/npm/issues/7742, maybe it will give you some ideas about how to solve it). The general advice is to avoid using the link: protocol. There's a suggested workaround mentioned in this SO answer https://stackoverflow.com/a/74133390/4429729. Alternatively, you can use yarn or pnpm in the workspaces (monorepo) mode to leverage its dedupe mechanism (there's some suggestions at the bottom of the NestJS FAQ page as well)

Papooch commented 1 year ago

I just confirmed that the pack method mentioned by the SO answer does work.

1) Run npm pack in the library folder 2) Link the generated .tgz file in the app's package.json

image

The application then bootstraps successfully, using only the app's own node_modules: image

alessandrosangalli commented 1 year ago

In real app my lib is published, so peer dependencies solved the problem. Thank you!