A lightweight inversion of control (IoC) container for JavaScript powered by TypeScript, supporting dependency injection, lifecycle management, aspect-oriented programming (AOP), and other enterprise-grade features.
- π Dependency Injection: Complete dependency injection support, including constructor injection and property injection
- π Lifecycle Management: Support for PreInject, PostInject, PreDestroy, and other lifecycle hooks
- π― Multiple Scopes: Support for Singleton, Transient, Global Shared Singleton, and other scopes
- βοΈ AOP Support: Complete aspect-oriented programming support, including Before, After, Around, and other advice types
- π Factory Pattern: Support for factory methods and custom instance creation strategies
- π Multiple Binding Types: Support for symbol binding, alias binding, value binding, and more
- π Expression Evaluation: Support for JSON path, environment variables, command-line arguments, and other expression evaluation
- π‘ Event System: Built-in event emitter supporting container lifecycle events
npm install @vgerbot/ioc reflect-metadataimport 'reflect-metadata';
import { ApplicationContext, Inject, Injectable } from '@vgerbot/ioc';
@Injectable()
class UserService {
getUser(id: number) {
return { id, name: 'John Doe' };
}
}
class UserController {
@Inject()
private userService!: UserService;
getUser(id: number) {
return this.userService.getUser(id);
}
}
const context = new ApplicationContext();
const controller = context.getInstance(UserController);
console.log(controller.getUser(1)); // { id: 1, name: 'John Doe' }class DatabaseService {
connect() {
console.log('Database connected');
}
}
class UserService {
constructor(
@Inject() private db: DatabaseService
) {}
getUsers() {
this.db.connect();
return ['user1', 'user2'];
}
}
const context = new ApplicationContext();
const userService = context.getInstance(UserService);
userService.getUsers();The IoC container supports multiple instance scopes that control how and when instances are created and managed. Understanding these scopes is crucial for proper resource management and application architecture.
| Scope | Description | Instance Lifecycle | Use Cases |
|---|---|---|---|
| Singleton | One instance per container | Created once, reused within container | Services, repositories, configurations |
| Transient | New instance every time | Created on each request | Stateless operations, temporary objects |
| Global Shared Singleton | One instance across all containers | Created once, shared globally | Global state, system-wide configurations |
import { Scope, InstanceScope } from '@vgerbot/ioc';
@Scope(InstanceScope.SINGLETON)
class DatabaseConnection {
private static connectionCount = 0;
constructor() {
DatabaseConnection.connectionCount++;
console.log(`Connection #${DatabaseConnection.connectionCount} created`);
}
}
const context = new ApplicationContext();
const conn1 = context.getInstance(DatabaseConnection);
const conn2 = context.getInstance(DatabaseConnection);
console.log(conn1 === conn2); // true - same instance@Scope(InstanceScope.TRANSIENT)
class HttpRequest {
private id = Math.random().toString(36);
getId() {
return this.id;
}
}
const context = new ApplicationContext();
const req1 = context.getInstance(HttpRequest);
const req2 = context.getInstance(HttpRequest);
console.log(req1.getId() === req2.getId()); // false - different instances
console.log(req1 === req2); // false@Scope(InstanceScope.GLOBAL_SHARED_SINGLETON)
class GlobalConfig {
private config = { version: '1.0.0' };
getVersion() {
return this.config.version;
}
updateVersion(version: string) {
this.config.version = version;
}
}
const context1 = new ApplicationContext();
const context2 = new ApplicationContext();
const config1 = context1.getInstance(GlobalConfig);
const config2 = context2.getInstance(GlobalConfig);
console.log(config1 === config2); // true - shared across containers
config1.updateVersion('2.0.0');
console.log(config2.getVersion()); // '2.0.0' - shared stateimport { PreInject, PostInject, PreDestroy } from '@vgerbot/ioc';
class DatabaseService {
connect() {
console.log('Database connected');
}
}
class UserService {
@Inject()
private db!: DatabaseService;
@PreInject()
beforeInject() {
console.log('Before injection - db is', this.db); // undefined
}
@PostInject()
afterInject() {
console.log('After injection - db is', this.db); // DatabaseService instance
this.db.connect();
}
@PreDestroy()
beforeDestroy() {
console.log('Cleaning up UserService');
}
}import { Aspect, JoinPoint, UseAspects, Advice } from '@vgerbot/ioc';
class LoggingAspect implements Aspect {
execute(joinPoint: JoinPoint) {
console.log(`Calling method: ${joinPoint.method}`);
console.log(`Arguments:`, joinPoint.args);
}
}
class UserService {
@UseAspects(Advice.Before, [LoggingAspect])
getUser(id: number) {
return { id, name: 'John Doe' };
}
}import { ProceedingAspect, ProceedingJoinPoint } from '@vgerbot/ioc';
class TimingAspect implements ProceedingAspect {
execute(joinPoint: ProceedingJoinPoint) {
const start = Date.now();
const result = joinPoint.proceed();
const duration = Date.now() - start;
console.log(`Method ${joinPoint.method} took ${duration}ms`);
return result;
}
}
class UserService {
@UseAspects(Advice.Around, [TimingAspect])
getUser(id: number) {
// Simulate async operation
return new Promise(resolve => {
setTimeout(() => resolve({ id, name: 'John Doe' }), 100);
});
}
}class TransformAspect implements Aspect {
execute(joinPoint: JoinPoint) {
// Modify return value
return {
...joinPoint.returnValue,
transformed: true
};
}
}
class UserService {
@UseAspects(Advice.AfterReturn, [TransformAspect])
getUser(id: number) {
return { id, name: 'John Doe' };
}
}import { Factory } from '@vgerbot/ioc';
class DatabaseConfig {
constructor(public host: string, public port: number) {}
}
class ConfigFactory {
@Factory('database-config')
createDatabaseConfig(): DatabaseConfig {
return new DatabaseConfig('localhost', 5432);
}
}
const context = new ApplicationContext();
const config = context.getInstance('database-config') as DatabaseConfig;
console.log(config.host); // 'localhost'import { Bind, Injectable } from '@vgerbot/ioc';
const USER_SERVICE = Symbol('UserService');
@Bind(USER_SERVICE)
class UserService {
getUsers() {
return ['user1', 'user2'];
}
}
@Injectable({ produce: USER_SERVICE })
class MockUserService {
getUsers() {
return ['mock-user1', 'mock-user2'];
}
}
const context = new ApplicationContext();
// Get instance by symbol
const userService = context.getInstance(USER_SERVICE);import { Value, Env } from '@vgerbot/ioc';
class ApiService {
@Value('config.api.baseUrl')
private baseUrl!: string;
@Env('NODE_ENV')
private environment!: string;
getApiUrl() {
return `${this.baseUrl}/api`;
}
}
const context = new ApplicationContext();
// Set JSON data
context.recordJSONData('config', {
api: {
baseUrl: 'https://api.example.com'
}
});
const apiService = context.getInstance(ApiService);
console.log(apiService.getApiUrl()); // https://api.example.com/apiimport { PartialInstAwareProcessor } from '@vgerbot/ioc';
class CustomInstAwareProcessor implements PartialInstAwareProcessor {
beforeInstantiation<T>(constructor: any, args: unknown[]): T | undefined | void {
console.log(`Creating instance of ${constructor.name}`);
// Can return custom instance or undefined to let container continue
}
afterInstantiation<T extends object>(instance: T): T {
console.log(`Instance created: ${instance.constructor.name}`);
// Can modify instance and return it
return instance;
}
}
const context = new ApplicationContext();
context.registerInstAwareProcessor(CustomInstAwareProcessor);const context = new ApplicationContext();
// Listen for pre-destroy events
context.onPreDestroy(() => {
console.log('Container is about to be destroyed');
});
// Listen for specific instance pre-destroy events
context.onPreDestroyThat((instance) => {
console.log(`Instance ${instance.constructor.name} is about to be destroyed`);
});
// Destroy container
context.destroy();import { Inject } from '@vgerbot/ioc';
class UserService {
getUser(id: number) {
return { id, name: 'John Doe' };
}
}
function processUser(userService: UserService, userId: number) {
return userService.getUser(userId);
}
const context = new ApplicationContext();
// Auto-inject function parameters
const result = context.invoke(processUser, {
injections: [UserService, 42]
});
// Or use decorator to mark function parameters
function processUserWithDecorator(
@Inject() userService: UserService,
userId: number
) {
return userService.getUser(userId);
}
const result2 = context.invoke(processUserWithDecorator, {
args: [undefined, 42] // First parameter will be auto-injected
});import { ApplicationContext, InstanceScope } from '@vgerbot/ioc';
const context = new ApplicationContext({
// Default scope
defaultScope: InstanceScope.SINGLETON,
// Lazy mode
lazyMode: true
});@Injectable(options?)- Mark injectable class@Inject(identifier?)- Dependency injection decorator@Scope(scope)- Set instance scope@Bind(identifier)- Bind identifier@Factory(identifier)- Factory method@Value(path)- Value injection@Env(key)- Environment variable injection@PreInject()- Pre-injection hook@PostInject()- Post-injection hook@PreDestroy()- Pre-destroy hook@UseAspects(advice, aspects)- AOP aspects
ApplicationContext- Application context and containerInstanceScope- Instance scope enumerationAdvice- AOP advice type enumeration
Aspect- Aspect interfaceProceedingAspect- Around advice interfaceInstanceResolution- Instance resolution interfaceEvaluator- Expression evaluator interface
Contributions are welcome! Please check the GitHub repository for more information.
This project is licensed under the MIT License.