inversify: how to handle binding to a generic interface/class - node.js

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.

Related

Cannot find module when using type from another module in class-validator

I'm using typescript on both frontend and backend, so I wanted to create a "shared types" package for them. For the backend I'm using nest.js and I recently ran into an issue with the class-validator package.
In my shared types package I created the following enum-like type (since enums itself don't seem to be working if they are being used from a node module):
export const MealTypes = {
BREAKFAST: 'Breakfast',
LUNCH: 'Lunch',
DINNER: 'Dinner',
SNACK: 'Snack'
} as const;
export type ObjectValues<T> = T[keyof T];
export type MealType = ObjectValues<typeof MealTypes>;
I've installed the module locally using npm i and I'm able to import the type in my backend like this:
import { MealType, MealTypes } from '#r3xc1/shared-types';
Since I am not able to use this constant for the IsEnum class validator, I wrote my own:
#ValidatorConstraint({ name: 'CheckEnum', async: false })
export class CheckEnumValidator implements ValidatorConstraintInterface {
validate(value: string | number, validationArguments: ValidationArguments) {
return Object.values(validationArguments.constraints[0]).includes(value);
}
defaultMessage(args: ValidationArguments) {
return `Must be of type XYZ`;
}
}
and then I'm using it in a DTO class like this:
export class CreateMealDTO {
#Validate(CheckEnumValidator, [MealTypes])
#IsNotEmpty()
meal_type: MealType;
}
But as soon as I add the #Validate(...) I get the following error on start:
Error: Cannot find module '#r3xc1/shared-types'
It only does this, if I am passing a type that has been imported from a node module into a validator. It also happens with other validators like IsEnum.
I'm not really sure why this error is happening and I appreciate any hints or help!

node/typescript: how to ensure imports with side effects are run?

I am writing a node app in typescript. I have written a class decorator #myDecorator, and the purpose of #myDecorator is such that I need to keep track of all the classes to which it's applied.
My question: how do I make sure all of those decorated classes are loaded before making use of that behavior? Some example code will help to make this more concrete:
Declaration of the decorator in file myDecorator.ts:
type ConstructorType = { new (...args: any[]): any };
// keep track of decorated classes
const registeredClasses: Map<string, ConstructorType> = new Map();
// class decorator
export function myDecorator<T extends ConstructorType>(targetConstructor: T) {
// create the modified class
const newClass = class extends targetConstructor { /* ... */ }
// register the modified class
registeredClasses.set(targetConstructor.name, newClass);
// and return it
return newClass;
}
export function doSomethingWithMyDecoratedClasses() {
//... some behavior that relies on `registeredClasses`
}
Declaration of a decorated class in file someClass.ts
import {myDecorator} from 'myDecorator.ts'
#myDecorator
class SomeClass { /* ... */ }
Making use of doSomethingWithMyDecoratedClasses in anotherFile.ts:
import { doSomethingWithMyDecoratedClasses } from 'myDecorator.ts`
//...
doSomethingWithMyDecoratedClasses()
//...
The problem is that I need to make sure that SomeClass has been added to registeredClasses before I make this call to doSomethingWithMyDecoratedClasses. And, in fact, I've written a number of such classes in my app, and I need to make sure they are all registered.
My current best understanding is that I need to call import 'someClass.ts' in anotherFile.ts (and, in fact, import all files where decorated classes are declared), so really I need to import someClass1.ts, import someClass2.ts, ...
Is that the best/only approach? Is there a recommended pattern for doing so?
Most applications have an index file that is responsible for importing the top level things. If you import doSomethingWithMyDecoratedClasses there, you'll guarantee that everything else is imported first.
An alternative would be to not call it in the root level of a module, and instead wait for an event.

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

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.

How to properly design API module in TypeScript?

I want to design a TypeScript (2.7) module for accessing external IS, let's call it InfoSys. I used the following approach.
I created info-sys.ts which defines a API class and related interfaces and enums, like:
class Api {
constructor(private endpoint: Endpoint) {
// ...
}
}
enum Endpoint {
CONTACTS = "contacts"
}
interface Contact {
name: string;
}
Now I want to export all the stuff under specific name. So I appended the export statement:
export const InfoSys = {
Api,
Endpoint,
Contact
};
When I try to use the module in another file, like:
import { InfoSys } from "info-sys";
// this line throws error: "Cannot find namespace 'InfoSys'"
private api: InfoSys.Api;
// but this line is ok
api = new InfoSys.Api(InfoSys.Endpoint.CONTACTS);
The way that works is the following - to export every piece individually:
export class Api {
constructor(private endpoint: Endpoint) {
// ...
}
}
export enum Endpoint {
CONTACTS = "contacts"
}
export interface Contact {
name: string;
}
and import them all to a single variable:
import * as InfoSys from "info-sys";
But the name of the variable can be whatever. It is not critical for functionality but I want to force developers, who will use the info-sys module, to use a specific name while accessing it (for easier readability and maintainability). How to properly design such module?
You can use namespace:
export namespace InfoSys {
Api,
Endpoint,
Contact
};
In general, this approach should be avoided. But in your case, it is fine as you are delivering things that are tightly related.
If Api is the single entry point to all these, I would also recommend this:
export class InfoSysApi { ... }
export namespace InfoSysApi {
export enum Endpoint = { ... }
export interface Contact { ... }
}
UPDATE:
To make sure I get the point through, DON'T do the following:
export namespace Foo {
export function X() { return 'x' }
export function Y() { return 'y' }
}
Only use export namespace to export "tugged in types", not values.
In TypeScript handbook: https://www.typescriptlang.org/docs/handbook/declaration-merging.html
Although the table says namespace can contain values, it is considered bad practice if you are writing ESM (import/export).
Namespace and ESM are two different mechanisms to achieve similar result.
Don't mix them together.

Typescript IOC in case of node

I am wondering how would you use typescript IOC specifically node app.
In case of external module-based architecture there is no any classes in the app. Just pure modules because my app heavily depends on node_modules.
How would I integrate IOC solution in such case? Any thoughts?
Here is my specific case I want to use IOC for:
I have mongoose model:
interface IStuffModel extends IStuff, mongoose.Document { }
var Stuff= mongoose.model<IStuffModel>('Stuff', Schemas.stuffSchema);
export = Stuff;
And related fake class:
export class Stuff implements IStuff {
//do stuff
}
How would I integrate IOC solution in such case
Here is a very popular library that I recommend : https://github.com/inversify/InversifyJS
External modules
Using external modules doesn't change the code at all. Instead of
kernel.bind(new TypeBinding<FooBarInterface>("FooBarInterface", FooBar));
Production
You just have
import {ProdFooBar} from "./prodFooBar";
kernel.bind(new TypeBinding<FooBarInterface>("FooBarInterface", ProdFooBar));
Test
import {MockFooBar} from "./mockFooBar";
kernel.bind(new TypeBinding<FooBarInterface>("FooBarInterface", MockFooBar));
As Basarat indicated in his answer, I have developed an IoC container called InversifyJS with advanced dependency injection features like contextual bindings.
You need to follow 3 basic steps to use it:
1. Add annotations
The annotation API is based on Angular 2.0:
import { injectable, inject } from "inversify";
#injectable()
class Katana implements IKatana {
public hit() {
return "cut!";
}
}
#injectable()
class Shuriken implements IShuriken {
public throw() {
return "hit!";
}
}
#injectable()
class Ninja implements INinja {
private _katana: IKatana;
private _shuriken: IShuriken;
public constructor(
#inject("IKatana") katana: IKatana,
#inject("IShuriken") shuriken: IShuriken
) {
this._katana = katana;
this._shuriken = shuriken;
}
public fight() { return this._katana.hit(); };
public sneak() { return this._shuriken.throw(); };
}
2. Declare bindings
The binding API is based on Ninject:
import { Kernel } from "inversify";
import { Ninja } from "./entities/ninja";
import { Katana } from "./entities/katana";
import { Shuriken} from "./entities/shuriken";
var kernel = new Kernel();
kernel.bind<INinja>("INinja").to(Ninja);
kernel.bind<IKatana>("IKatana").to(Katana);
kernel.bind<IShuriken>("IShuriken").to(Shuriken);
export default kernel;
3. Resolve dependencies
The resolution API is based on Ninject:
import kernel = from "./inversify.config";
var ninja = kernel.get<INinja>("INinja");
expect(ninja.fight()).eql("cut!"); // true
expect(ninja.sneak()).eql("hit!"); // true
The latest release (2.0.0) supports many use cases:
Kernel modules
Kernel middleware
Use classes, string literals or Symbols as dependency identifiers
Injection of constant values
Injection of class constructors
Injection of factories
Auto factory
Injection of providers (async factory)
Activation handlers (used to inject proxies)
Multi injections
Tagged bindings
Custom tag decorators
Named bindings
Contextual bindings
Friendly exceptions (e.g. Circular dependencies)
You can learn more about it at https://github.com/inversify/InversifyJS
In the particular context of Node.js there is a hapi.js example that uses InversifyJS.

Resources