
Security News
vlt Launches "reproduce": A New Tool Challenging the Limits of Package Provenance
vlt's new "reproduce" tool verifies npm packages against their source code, outperforming traditional provenance adoption in the JavaScript ecosystem.
@travetto/di
Advanced tools
Install: @travetto/di
npm install @travetto/di
# or
yarn add @travetto/di
Dependency injection is a framework primitive. When used in conjunction with automatic file scanning, it provides for handling of application dependency wiring. Due to the nature of Typescript and type erasure of interfaces, dependency injection only supports class
es as a type signifier. The primary goal of dependency injection is to allow for separation of concerns of object creation and it's usage.
The @Injectable and @InjectableFactory decorators provide the registration of dependencies. Dependency declaration revolves around exposing class
es and subtypes thereof to provide necessary functionality. Additionally, the framework will utilize dependencies to satisfy contracts with various implementation.
Code: Example Injectable
import { Injectable } from '@travetto/di';
@Injectable()
class CustomService {
async coolOperation() {
// Do work!
}
}
When declaring a dependency, you can also provide a token to allow for multiple instances of the dependency to be defined. This can be used in many situations:
Code: Example Injectable with multiple targets
import { Injectable, Inject } from '@travetto/di';
@Injectable()
class CustomService {
async coolOperation() {
// do work!
}
}
const CUSTOM2 = Symbol.for('di-custom2');
@Injectable({ target: CustomService, qualifier: CUSTOM2 })
class CustomService2 extends CustomService {
override async coolOperation() {
await super.coolOperation();
// Do some additional work
}
}
class Consumer {
@Inject(CUSTOM2) // Pull in specific service
service: CustomService;
}
As you can see, the target
field is also set, which indicates to the dependency registration process what class
the injectable is compatible with. Additionally, when using abstract
classes, the parent class
is always considered as a valid candidate type.
Code: Example Injectable with target via abstract class
import { Injectable } from '@travetto/di';
abstract class BaseService {
abstract work(): Promise<void>;
}
@Injectable()
class SpecificService extends BaseService {
async work() {
// Do some additional work
}
}
In this scenario, SpecificService
is a valid candidate for BaseService
due to the abstract inheritance. Sometimes, you may want to provide a slight variation to a dependency without extending a class. To this end, the @InjectableFactory decorator denotes a static
class method that produces an @Injectable.
Code: Example InjectableFactory
import { InjectableFactory } from '@travetto/di';
// Not injectable by default
class CoolService {
}
class Config {
@InjectableFactory()
static initService() {
return new CoolService();
}
}
Given the static
method initService
, the function will be provided as a valid candidate for CoolService
. Instead of calling the constructor of the type directly, this function will work as a factory for producing the injectable.
Note: Other modules are able to provide aliases to @Injectable that also provide additional functionality. For example, the Configuration module @Config or the RESTful API module @Controller decorator registers the associated class as an injectable element.
Once all of your necessary dependencies are defined, now is the time to provide those @Injectable instances to your code. There are three primary methods for injection:
The @Inject decorator, which denotes a desire to inject a value directly. These will be set post construction.
Code: Example Injectable with dependencies as @Inject fields
import { Injectable, Inject } from '@travetto/di';
import { DependentService } from './dep';
@Injectable()
class CustomService {
@Inject()
private dependentService: DependentService;
async coolOperation() {
await this.dependentService.doWork();
}
}
The @Injectable constructor params, which will be provided as the instance is being constructed.
Code: Example Injectable with dependencies in constructor
import { Injectable } from '@travetto/di';
import { DependentService } from './dep';
@Injectable()
class CustomService {
constructor(private dependentService: DependentService) { }
async coolOperation() {
await this.dependentService.doWork();
}
}
Via @InjectableFactory params, which are comparable to constructor params
Code: Example InjectableFactory with parameters as dependencies
import { InjectableFactory } from '@travetto/di';
import { DependentService, CustomService } from './dep';
class Config {
@InjectableFactory()
static initService(dependentService: DependentService) {
return new CustomService(dependentService);
}
}
If you are building modules for others to consume, often times it is possible to end up with multiple implementations for the same class.
Code: Example Multiple Candidate Types
import { Injectable, Inject } from '@travetto/di';
export abstract class Contract {
}
@Injectable()
class SimpleContract extends Contract { }
@Injectable()
export class ComplexContract extends Contract { }
@Injectable()
class ContractConsumer {
// Will default to SimpleContract if nothing else registered
@Inject()
contract: Contract;
}
By default, if there is only one candidate without qualification, then that candidate will be used. If multiple candidates are found, then the injection system will bail. To overcome this the end user will need to specify which candidate type should be considered primary
:
Code: Example Multiple Candidate Types
import { InjectableFactory } from '@travetto/di';
import { Contract, ComplexContract } from './injectable-multiple-default';
class Config {
// Complex will be marked as the available Contract
@InjectableFactory({ primary: true })
static getContract(complex: ComplexContract): Contract {
return complex;
}
}
Some times you will need to lookup a dependency dynamically, or you want to control the injection process at a more granular level. To achieve that you will need to directly access the DependencyRegistry. The registry allows for requesting a dependency by class reference:
Code: Example of Manual Lookup
import { Injectable, DependencyRegistry } from '@travetto/di';
@Injectable()
class Complex { }
class ManualLookup {
async invoke() {
const complex = await DependencyRegistry.getInstance(Complex);
return complex;
}
}
Additionally, support for interfaces (over class inheritance) is provided, but requires binding the interface to a concrete class as the interface does not exist at runtime.
Code: Example Interface Injection
import { DependencyRegistry, Inject, Injectable, InjectableFactory } from '@travetto/di';
class TargetConcrete { }
/**
* @concrete .:TargetConcrete
*/
export interface ServiceContract {
deleteUser(userId: string): Promise<void>;
}
class MyCustomService implements ServiceContract {
async deleteUser(userId: string): Promise<void> {
// Do something
}
}
@Injectable()
class SpecificService {
@Inject()
service: ServiceContract;
}
class ManualInvocationOfInterface {
@InjectableFactory()
static getCustomService(): Promise<ServiceContract> {
return DependencyRegistry.getInstance<ServiceContract>(TargetConcrete);
}
}
FAQs
Dependency registration/management and injection support.
The npm package @travetto/di receives a total of 64 weekly downloads. As such, @travetto/di popularity was classified as not popular.
We found that @travetto/di demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer 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.
Security News
vlt's new "reproduce" tool verifies npm packages against their source code, outperforming traditional provenance adoption in the JavaScript ecosystem.
Research
Security News
Socket researchers uncovered a malicious PyPI package exploiting Deezer’s API to enable coordinated music piracy through API abuse and C2 server control.
Research
The Socket Research Team discovered a malicious npm package, '@ton-wallet/create', stealing cryptocurrency wallet keys from developers and users in the TON ecosystem.