thiagobustamante / typescript-ioc

A Lightweight annotation-based dependency injection container for typescript.
MIT License
526 stars 64 forks source link

"TypeError: Invalid type requested to IoC container. Type is not defined." - improve error message? #54

Open seiyria opened 4 years ago

seiyria commented 4 years ago

Been using this library for a while, and it's pretty great compared to the other IoC libraries I've seen. My only major problem is this error - I never have any idea what it's trying to inject and failing to inject. Can this error message be improved so I can track down what it's trying to inject, so I can actually fix it instead of guessing randomly?

Given some advice, I would happily implement this myself as well. I just don't know if this is something that can be done easily or where to approach it, as I've never touched the internals of a DI framework.

thiagobustamante commented 4 years ago

I totally agree that this message is not good. But I just don't know how to give more information.

The only place where this error is raised is:

    public static checkType(source: Object) {
        if (!source) {
            throw new TypeError('Invalid type requested to IoC ' +
                'container. Type is not defined.');
        }
    }

And checkType is called when you are trying to bind a new class to the container:

So, this message occurs if you call Container.bind or Container.to passing a null or undefined type. We don't have more information than this

Kampfmoehre commented 4 years ago

We have a case here, where this error is not helpful at all.

We have a common base project which some of our other projects get via NPM. This project defines a BaseClass that gets some services via typescript-ioc and a Wrapper that initializes this class. The wrapper receives the base class via injection. Now in the downstream projects we extend from that base class and set our container configuration to use the extended class when asking for the base class. This produces the error mentioned above.

Some code: Base package baseapp.ts

import { SomeService } from "./someservice";
import { Inject } from "typescript-ioc";

export abstract class BaseApp {
  @Inject protected service1: SomeService;

  public async bootstrap() {
    // some base logic that all downstream projects need
    await this.service1.init();
    await this.customBootstrap();
  }

  protected abstract customBootstrap(): Promise<void>;
}

Base package applicationwrapper.ts


import { BaseApp } from "./baseapp";
import { Inject } from "typescript-ioc";

export class ApplicationWrapper {
  @Inject private app: BaseApp;

  public async boot() {
    // ...
    await this.app.bootstrap();
  }
}

Base package index.ts

import { BaseApp } from "./baseapp";
import { ApplicationWrapper } from "./applicationwrapper";

export { BaseApp, ApplicationWrapper };

Downstream project app.ts

import { BaseApp } from "base-package";
import { Inject } from "typescript-ioc";
import { SomeOtherService } from "./someotherservice";

export class App extends BaseApp {
  @Inject private service2: SomeOtherService;
  protected async customBootstrap(): Promise<void> {
    await this.service2.init();
  }
}

Downstream project ioc.ts

import { BaseApp } from "base-package";
import { Container } from "typescript-ioc";
import { App } from "./app";

export function configureContainer() {
  Container.bind(BaseApp).to(App);
}

Downstream project index.ts

import { ApplicationWrapper } from "base-package";
import { App } from "./app";
import { configureContainer } from "./ioc";

configureContainer();
const wrapper = new ApplicationWrapper();
wrapper.boot();

The problem occurs in the wrapper when the app Property is used for the first time in the wrapper class.

Now I 'm just guessing here, but I think the use of typescript-ioc in both packages is not working as intended. For Node the class BaseClass in the base package is somehow not the same as the imported BaseClass in the derived package. Maybe this has something to do with the way require works, putting the complete path to a module in it's name.

We tried importing the BaseClass directly via import { BaseClass } from "base-module"; as well as import the file directly like import { BaseClass } from "base-module/dist/baseclass" but non of them works.

So I have no idea, why the type is not defined in this case, but @thiagobustamante is right, somehow the source argument is undefined.

Is there anything we can change to make it work? Right now the only solution is, to not use Inject in the Wrapper, but to instantiate the App class and provide it as argument to the ApplicationWrapper. Is injecting over multiple packages supported at all?

nemcikjan commented 4 years ago

We have a case here, where this error is not helpful at all.

We have a common base project which some of our other projects get via NPM. This project defines a BaseClass that gets some services via typescript-ioc and a Wrapper that initializes this class. The wrapper receives the base class via injection. Now in the downstream projects we extend from that base class and set our container configuration to use the extended class when asking for the base class. This produces the error mentioned above.

Some code: Base package baseapp.ts

import { SomeService } from "./someservice";
import { Inject } from "typescript-ioc";

export abstract class BaseApp {
  @Inject protected service1: SomeService;

  public async bootstrap() {
    // some base logic that all downstream projects need
    await this.service1.init();
    await this.customBootstrap();
  }

  protected abstract customBootstrap(): Promise<void>;
}

Base package applicationwrapper.ts

import { BaseApp } from "./baseapp";
import { Inject } from "typescript-ioc";

export class ApplicationWrapper {
  @Inject private app: BaseApp;

  public async boot() {
    // ...
    await this.app.bootstrap();
  }
}

Base package index.ts

import { BaseApp } from "./baseapp";
import { ApplicationWrapper } from "./applicationwrapper";

export { BaseApp, ApplicationWrapper };

Downstream project app.ts

import { BaseApp } from "base-package";
import { Inject } from "typescript-ioc";
import { SomeOtherService } from "./someotherservice";

export class App extends BaseApp {
  @Inject private service2: SomeOtherService;
  protected async customBootstrap(): Promise<void> {
    await this.service2.init();
  }
}

Downstream project ioc.ts

import { BaseApp } from "base-package";
import { Container } from "typescript-ioc";
import { App } from "./app";

export function configureContainer() {
  Container.bind(BaseApp).to(App);
}

Downstream project index.ts

import { ApplicationWrapper } from "base-package";
import { App } from "./app";
import { configureContainer } from "./ioc";

configureContainer();
const wrapper = new ApplicationWrapper();
wrapper.boot();

The problem occurs in the wrapper when the app Property is used for the first time in the wrapper class.

Now I 'm just guessing here, but I think the use of typescript-ioc in both packages is not working as intended. For Node the class BaseClass in the base package is somehow not the same as the imported BaseClass in the derived package. Maybe this has something to do with the way require works, putting the complete path to a module in it's name.

We tried importing the BaseClass directly via import { BaseClass } from "base-module"; as well as import the file directly like import { BaseClass } from "base-module/dist/baseclass" but non of them works.

So I have no idea, why the type is not defined in this case, but @thiagobustamante is right, somehow the source argument is undefined.

Is there anything we can change to make it work? Right now the only solution is, to not use Inject in the Wrapper, but to instantiate the App class and provide it as argument to the ApplicationWrapper. Is injecting over multiple packages supported at all?

If you are using the library in your base project you have to reexport it and import in your downstream project. The problem importing in both projects from typescript-ioc is, that there two containers created, one for each project. We laso faced this issue and solution was creating a 'fake' path infrastructure ioc in base module which only reexports: export * from 'typescript-ioc' and then you should import from base_module/infrastructure/ioc. Hope this solves your trouble

Kampfmoehre commented 4 years ago

Yes I fixed some other problems by moving typescript-ioc from dependency to peer-dependency in the base project. But the problem with injecting a class that is extended in the downstream project remains. I think the BaseApp class that I configure in the extended project is somehow not the same BaseApp class in the transpiled JavaScript in the node_modules/base-package.