How do I use an IOC container with typescript generics in node? - node.js

I am attempting to use container-ioc, but am happy to use any package supplying IoC. I cannot find any examples that use typescript generics. Basically, I want:
interface A<T> {
foo();
}
class A<T> {
foo() {
}
}
interface B<T> {
bar: A<T>;
}
class B<T> {
bar: A<T>;
constructor(param: A<T>) {
this.bar = param;
}
}
where I can set up an IoC container to inject A into B. I am using typescript in a node app. I have syntax that seems to parse at least, but cannot craft the container resolve() as I don't know how to pass the generic parameter. Not to mention, I'm not sure whether this is actually supported.

So, I have a workaround that works for my situation. Firstly, I set a symbol for the generic type with const MyFoo = Symbol("Foo<My>"). Then, I use a factory constructor when registering the type:
container.register([
{ token: MyBar, useFactory: () => new Bar<My>() },
token: MyFoo, useFactory: () => new Foo<My>( container.resolve(MyBar) )}]);
Finally, I do not #inject the parameter on the constructor. This method means that I need to know what generic types I will be using beforehand. In my case, I do: my generic types implement an entity repository and related classes, and my entities are the generic parameters. I'd prefer to not have yet another place to maintain when setting up new entities, so I am leaving this answer unaccepted in case someone has a better solution.

Related

Proper way to manually instantiate Nest.js providers

I think I might be misunderstanding Nest.js's IoC container, or maybe DI as a whole.
I have a class, JSONDatabase, that I want to instantiate myself based on some config value (can either be JSON or SQL).
My DatabaseService provider:
constructor(common: CommonService, logger: LoggerService) {
// eslint-disable-next-line prettier/prettier
const databaseType: DatabaseType = common.serverConfig.dbType as DatabaseType;
if (databaseType === DatabaseType.JSON) {
this.loadDatabase<JSONDatabase>(new JSONDatabase());
} else if (databaseType === DatabaseType.SQL) {
this.loadDatabase<SQLDatabase>(new SQLDatabase());
} else {
logger.error('Unknown database type.');
}
}
My JSONDatabase class:
export class JSONDatabase implements IDatabase {
dbType = DatabaseType.JSON;
constructor(logger: LoggerService, io: IOService) {
logger.log(`Doing something...`)
}
}
However, the problem with this is that if I want my JSONDatabase to take advantage of injection, ie. it requires both IOService and LoggerService, I need to add the parameters from the DatabaseService constructor rather than inject them through Nest's IoC containers.
Expected 2 arguments, but got 0 [ts(2554)]
json.database.ts(7, 15): An argument for 'logger' was not provided.
Is this the proper way to do this? I feel like manually passing these references through is incorrect, and I should use Nest's custom providers, however, I don't really understand the Nest docs on this subject. I essentially want to be able to new JSONDatabase() without having to pass in references into the constructor and have the Nest.js IoC container inject the existing singletons already (runtime dependency injection?).
I might be completely off base with my thinking here, but I haven't used Nest all that much, so I'm mostly working off of instinct. Any help is appreciated.
The issue you have right now is because you are instantiating JSONDatabase manually when you call new JSONDatabase() not leveraging the DI provided by NestJS. Since the constructor expects 2 arguments (LoggerService, and IOService) and you are providing none, it fails with the message
Expected 2 arguments, but got 0 [ts(2554)]
I think depending on your use case you can try a couple of different options
If you fetch your configuration on startup and set the database once in the application lifetime you can use use a Custom provider with the useFactory syntax.
const providers = [
{
provide: DatabaseService,
useFactory: (logger: LoggerService, io: IOService, config: YourConfigService): IDatabase => {
if (config.databaseType === DatabaseType.JSON) {
return new JSONDatabase(logger, io);
} else if (databaseType === DatabaseType.SQL) {
return new SQLDatabase(logger, io);
} else {
logger.error('Unknown database type.');
}
},
inject: [LoggerService, IOService, YourConfigService]
},
];
#Module({
providers,
exports: providers
})
export class YourModule {}
If you have LoggerService, IOService and YourConfigurationService annotated with #Injectable() NestJS will inject them in the useFactory context. There you can check the databaseType and manually instantiate the correct IDatabase implementation. The drawback with this approach is that you can't easily change the database in runtime. (This might work just fine for your use case)
You can use strategy/factory pattern to get the correct implementation based on a type. Let say you have a method that saves to different databases based on an specific parameter.
#Injectable()
export class SomeService {
constructor(private readonly databaseFactory: DatabaseFactory){}
method(objectToSave: Object, type: DatabaseType) {
databaseFactory.getService(type).save(objectToSave);
}
}
#Injectable()
export class DatabaseFactory {
constructor(private readonly moduleRef: ModuleRef) {}
getService(type: DatabaseType): IDatabase {
this.moduleRef.get(`${type}Database`);
}
}
The idea of the code above is, based on the database type, get the correct singleton from NestJS scope. This way it's easy to add a new database if you want - just add a new type and it's implementation. (and your code can handle multiple databases at the same time!)
I also believe you can simply pass the already injected LoggerService and IOService to the DatabasesService you create manually (You would need to add IOService as a dependency of DatabaseServce
#Injectable()
export class DatabaseService {
constructor(common: CommonService, logger: LoggerService, ioService: IOService) {
// eslint-disable-next-line prettier/prettier
const databaseType: DatabaseType = common.serverConfig.dbType as DatabaseType;
if (databaseType === DatabaseType.JSON) {
this.loadDatabase<JSONDatabase>(new JSONDatabase(logger, ioService));
} else if (databaseType === DatabaseType.SQL) {
this.loadDatabase<SQLDatabase>(new SQLDatabase(logger, ioService));
} else {
logger.error('Unknown database type.');
}
}
}

Possible to register static only protocols?

Unfortunately, some libraries only have static methods to them. Is it possible to make Swinject register a type to return a type instead of an instance of a type? Makes it hard to stub out static only libraries for testing.
In the current state of the Swinject this is unfortunately not possible. One of the ways you could work around this would be creating a custom type provider:
protocol LibraryProtocol {}
class Library: LibraryProtocol {}
class LibraryProvider {
let library: LibraryProtocol.Type
init(_ library: LibraryProtocol.Type) { self.library = library }
}
container.register(LibraryProvider.self) { _ in LibraryProvider(Library.self) }

inversify: how to handle binding to a generic interface/class

I am using inversify for an mean stack application developed with typescript. Following the instructions here at this url: https://www.npmjs.com/package/inversify, I created the inversify.config.ts file and added the code relevant to my needs. I am receiving the following error for one of my binding:
"Error:(39, 71) TS2349:Cannot invoke an expression whose type lacks a call signature. Type 'typeof ExampleRepository' has no compatible call signatures.".
inversify.config.ts:
myContainer.bind<IExampleRepository<IGroup>>(TYPES.IExampleRepository).to(ExampleRepository<IGroup>).whenTargetNamed("exampleRepository");
types.ts:
IExampleRepository: Symbol("ExampleRepository")
How would the inversify.config.ts entry have to change to accomodate this need? What am I doing wrong here? Can inversify handle this scenario?
I think that if your interface is generic IExampleRepository<T> then your ExampleRepository doesn't need the <IGroup> generic on it.
import { Container, injectable } from "inversify";
const TYPES = {
IExampleRepository: Symbol("IExampleRepository"),
};
class IGroup {
}
interface IExampleRepository<T> {
group: T;
}
#injectable()
class ExampleRepository implements IExampleRepository<IGroup> {
group: IGroup
}
const myContainer = new Container();
myContainer.bind<IExampleRepository<IGroup>>(TYPES.IExampleRepository).to(ExampleRepository).whenTargetNamed("exampleRepository");
`
Please provide more example code for IExampleRepository and Examplerepository. That might help get a better answer.

How do I improve this object design in Typescript?

I have created a class in Typescript that implements a simple stream (FRP). Now I want to extend it with client side functionality (streams of events). To illustrate my problem, here is some pseudo-code:
class Stream<T> {
map<U>(f: (value: T) => U): Stream<U> {
// Creates a new Stream instance that maps the values.
}
// Quite a few other functions that return new instances.
}
This class can be used both on the server and on the client. For the client side, I created a class that extends this one:
class ClientStream<T> extends Stream<T> {
watch(events: string, selector: string): Stream<Event> {
// Creates a new ClientStream instance
}
}
Now the ClientStream class knows about map but the Stream class doesn't know about watch. To circumvent this, functions call a factory method.
protected create<U>(.....): Stream<U> {
return new Stream<U>(.....)
}
The ClientStream class overrides this function to return ClientStream instances. However, the compiler complains that ClientStream.map returns a Stream, not a ClientStream. That can be 'solved' using a cast, but besides being ugly it prevents chaining.
Example code that exhibits this problem:
class Stream {
protected create(): Stream {
return new Stream()
}
map() {
return this.create()
}
}
class ClientStream extends Stream {
protected create(): ClientStream {
return new ClientStream()
}
watch() {
return this.create()
}
}
let s = new ClientStream().map().watch()
This does not compile because according to the compiler, the stream returned from map is not a ClientStream: error TS2339: Property 'watch' does not exist on type 'Stream'.
I don't really like this pattern, but I have no other solution that is more elegant. Things I've thought about:
Use composition (decorator). Not really an option given the number of methods I would have to proxy through. And I want to be able to add methods to Stream later without having to worry about ClientStream.
Mix Stream into ClientStream. More or less the same problem, ClientStream has to know the signatures of the functions that are going to be mixed in (or not? Please tell).
Merge these classes into one. This is a last resort, the watch function has no business being on the server.
Do you have a better (more elegant) solution? If you have an idea that gets closer to a more functional style, I'd be happy to hear about it. Thanks!
What you're trying to do is called F-bounded polymorphism.
In TypeScript this is done via the this keyword. Take a look at Typescript's documentation for polymorphic this types. If you follow the documentation, you should be able to implement what you want :-)
Actually, just make sure that you're returning this in your member methods and you should be fine!

Dependency injection php

I have built a simple dependency injection container that I pass around my classes that need it, everything works and all is good.
My question is that say I have 2 classes such as
class A {
public function __construct() {
}
}
class B {
public function __construct(A $a) {
}
}
Should I enforce the typehinting in the class itself or in the injection container such as;
$di->set('A', function() {
return new A();
});
$di->set('B', function(A $a) {
return new B($a);
});
Should I do both or either/or.
For answers why is it better to use one over the other etc?
Thanks.
I would use the first case, enforce the type hinting in the class itself.
This will make it clear for readers of the code what are the actual dependencies of the class.
If you decide to change the DI container (or hypothetically remove it) or reuse the classes in other project, it is good to have the type hinting in the class itself.
The DI container is there simply to help you manage dependencies.

Resources