zazoomauro / node-dependency-injection

The NodeDependencyInjection component allows you to standarize and centralize the way objects are constructed in your application.
https://github.com/zazoomauro/node-dependency-injection/wiki
MIT License
276 stars 34 forks source link

Autowiring seems only to work with private constructor properties #203

Closed abadb closed 1 year ago

abadb commented 1 year ago

Let's assume the following case:

  1. Using the container loading a YAML file
  2. Autowiring is set to true.

Then, If I have:

  1. An interface
export default interface QueryBus {
    ask<R extends Response>(query: Query): Promise<R>;
}
  1. An implementation of this interface

export default class SimpleQueryBus implements QueryBus {
    constructor(private readonly handlers: QueryHandlersRegistry) {

    }
}
  1. A client which uses this QueryBus interface:

    export default class SampleGetController implements ControllerBase {
    
    private queryBus: QueryBus;
    
    constructor(queryBus: QueryBus) {
        this.queryBus = queryBus;
    }
    }

If using autowiring (loading a YAML file):

services:

  _defaults:
    autowire: true

The const typeNameForArgument assignment will fail on the async _getDefinition(classDeclaration, body, parsedFile, ServiceClass) method:

for (const parameterDeclaration of constructorParams) {
        const typeNameForArgument = parameterDeclaration.parameter.typeAnnotation.typeAnnotation.typeName.name;
        const argumentId = await this._getIdentifierFromImports(typeNameForArgument, body, parsedFile);
        if (!argumentId) {
          continue;
        }
        definition.addArgument(new _Reference.default(argumentId), definition.abstract);
      }

When resolving the StatusGetController service, it will throw an exception.

I think this happens because the typescript-eslint parser returns a different data structure when resolving constructors which dont use private properties.

If using private constructor properties:


interface TestInterface {

}

class ExampleClass {

  constructor(private readonly parameter: TestInterface) {

  }

}

The parameterDeclaration.parameter.typeAnnotation.typeAnnotation.typeName.name will be present, as resolved by the typescript-eslint parser:

Captura de pantalla 2023-07-17 a las 16 42 00

However, if not using private property constructors, like:


interface TestInterface {

}

class ExampleClass {

  private thing: TestInterface;

  constructor(parameter: TestInterface) {
    this.thing = parameter;
  }

}

The parameterDeclaration.parameter.typeAnnotation.typeAnnotation.typeName.name will throw an error, because typescript-eslint resolves this structure to:

Captura de pantalla 2023-07-17 a las 16 46 41

And the parameter.typeAnnotation property does not exist when using this declaration.

So, I would suggest to change the documentation to reflect that autowiring only works with private constructor properties.

zazoomauro commented 1 year ago

@abadb Thanks! I'll update the documentation in order to reflect your suggestion. As well I will create a task in order to make it working with protected and public attributes on constructor

zazoomauro commented 1 year ago

https://github.com/zazoomauro/node-dependency-injection/wiki/Autowire