Research
Security News
Quasar RAT Disguised as an npm Package for Detecting Vulnerabilities in Ethereum Smart Contracts
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
JavaScript's dependency injection is like Autofac in .Net
npm i cheap-di
The recommended way of using this package is to use it with code transformers like cheap-di-ts-transform. Because in this way, you will get true dependency injection:
import { container } from 'cheap-di';
class MyLogger {
log(...message: unknown[]) {
console.log(...message);
}
}
class MyService {
constructor(private logger: MyLogger) {}
someStaff() {
this.logger.log('I do something');
}
}
/**
* With cheap-di-ts-transform here will be added metadata about Service dependencies.
* */
// in this case you have no abstractions, so you don't need to registrate something yourself,
// just resolve your target:
const myService = container.resolve(MyService);
if (myService) {
myService.someStaff();
}
If you can't use cheap-di-ts-transform, you have to use @inject
decorator to define your dependencies (it supports stage 2 and stage 3 TypeScript syntax):
import { container, inject } from 'cheap-di';
class MyLogger {
log(...message: unknown[]) {
console.log(...message);
}
}
@inject(MyLogger)
class MyService {
constructor(private logger: MyLogger) {}
someStaff() {
this.logger.log('I do something');
}
}
// in this case you have no abstractions, so you don't need to registrate something yourself,
// just resolve your target:
const myService = container.resolve(MyService);
if (myService) {
myService.someStaff();
}
Note: You may wonder why we use abstract classes?
- It is syntax analog of interface.
But why don't we use interfaces?
- Because we need some unique "token" compatible with JavaScript, to be able to register and resolve implementations. You can't use TypeScript interface in runtime (because it doesn't exists in JavaScript), and you can use classes!
Some another DI libraries use symbols to achieve that, but we don't see necessity to map somewhere symbols with implementations if we may use only classes everywhere. Less code -> less issues.
// logger.ts
abstract class Logger {
abstract debug: (message: string) => void;
}
class ConsoleLogger implements Logger {
constructor(public prefix: string) {}
debug(message: string) {
console.log(`${this.prefix}: ${message}`);
}
}
// service.ts
import { Logger } from './logger';
class Service {
constructor(private logger: Logger) {}
doSome() {
this.logger.debug('Hello world!');
}
}
// somewhere in your application initialization
import { container } from 'cheap-di';
import { Logger, ConsoleLogger } from './logger';
const myLogPrefix = 'INFO: ';
container.registerImplementation(ConsoleLogger).as(Logger).inject(myLogPrefix);
// somewhere inside your code
// or you may use some middleware to do this, to get rid of Service Locator antipattern
import { container } from 'cheap-di';
import { Service } from './service';
const service = container.resolve(Service);
service.doSome();
Without cheap-di-ts-transform, you still may use cheap-di with @inject
decorator (it supports stage 2 and stage 3 TypeScript syntax):
import { inject } from 'cheap-di';
abstract class SessionAccessor {
abstract getSession(): string;
}
abstract class Logger {
abstract debug(message: string): void;
}
abstract class InfoLogger extends Logger {}
abstract class ErrorLogger extends Logger {}
// non-classes-arguments specified as "unknown" (or any other string)
// we use `dependencies.filter((dependency) => typeof dependency !== 'string'))` code to filter non-clases dependencies
@inject('unknown', SessionAccessor)
class ConsoleLogger implements Logger {
constructor(public prefix: string, private sessionAccessor: SessionAccessor) {}
debug(message: string) {
console.log(`[${this.sessionAccessor.getSession()}] ${this.prefix}: ${message}`);
}
}
@inject(InfoLogger)
class Service {
constructor(private logger: InfoLogger) {}
doSome() {
this.logger.debug('Hello world!');
}
}
// somewhere
import { container } from 'cheap-di';
import { InfoLogger, ErrorLogger, ConsoleLogger } from './logger';
const infoPrefix = 'INFO: ';
container.registerImplementation(ConsoleLogger).as(InfoLogger).inject(infoPrefix);
const errorPrefix = 'ERROR: ';
container.registerImplementation(ConsoleLogger).as(ErrorLogger).inject(errorPrefix);
// somewhere in inside your code
// or you may use some middleware to do this, to get rid of Service Locator antipattern
import { container } from 'cheap-di';
import { Service } from './service';
const service = container.resolve(Service);
service.doSome();
To use stage 2 decorators, you need to adjust your tsconfig.json like this:
{
"compilerOptions": {
// ...
"experimentalDecorators": true
}
}
To use stage 3 decorators, you don't need extra setup.
If you would like to specify implementation of your interface:
import { container } from 'cheap-di';
abstract class Service {/**/}
class ServiceImpl extends Service {/**/}
container
.registerImplementation(ServiceImpl)
.as(Service);
Or if you want to inject some parameters to its constructor:
import { container } from 'cheap-di';
class Foo {
constructor(private name: string) {}
}
container
.registerImplementation(Foo)
.inject('some name');
Or if you want to have only one instance of the implementation class:
import { container } from 'cheap-di';
class Foo {}
container
.registerImplementation(Foo)
.asSingleton();
And singletons may also be used with interface specifications:
import { container } from 'cheap-di';
abstract class Service {/**/}
class ServiceImpl extends Service {/**/}
container
.registerImplementation(ServiceImpl)
.asSingleton(Service);
And even with argument injection:
import { container } from 'cheap-di';
abstract class Service {/**/}
class ServiceImpl extends Service {
constructor(private name: string) {
super();
}
}
container
.registerImplementation(ServiceImpl)
.asSingleton(Service)
.inject('some name');
If you want to register some instance as an interface, the result is similar to singleton registration, except that you have to instantiate the class yourself.
import { container } from 'cheap-di';
class Database {
get() {
return Promise.resolve('name1');
}
}
container
.registerInstance(new Database())
.as(Database);
And you may use any object value as instance
import { container } from 'cheap-di';
abstract class Database {
abstract get(): Promise<string>;
}
const db: Database = {
async get() {
return Promise.resolve('name1');
},
};
container
.registerInstance(db)
.as(Database);
You can see more examples of container methods in ContainerImpl.test.ts
FAQs
Easy way to create nice web routes for you application
The npm package cheap-di receives a total of 76 weekly downloads. As such, cheap-di popularity was classified as not popular.
We found that cheap-di demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.