Skip to content

vgerbot-libraries/ioc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

@vgerbot/ioc

Codacy Badge Codacy Badge

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.

🌟 Key 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

πŸ“¦ Installation

npm install @vgerbot/ioc reflect-metadata

πŸš€ Quick Start

Basic Usage

import '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' }

Constructor Injection

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();

🎯 Scope Management

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 Types Overview

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

Singleton

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

Transient

@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

Global Shared Singleton

@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 state

πŸ”„ Lifecycle Management

import { 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');
    }
}

βœ‚οΈ Aspect-Oriented Programming (AOP)

Before Advice

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' };
    }
}

Around Advice

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);
        });
    }
}

AfterReturn Advice

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' };
    }
}

🏭 Factory Pattern and Binding

Factory Methods

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'

Symbol Binding

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);

Value Binding

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/api

πŸŽ›οΈ Advanced Features

Custom Instantiation Processor

import { 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);

Event Listening

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();

Function Invocation with Injection

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
});

πŸ”§ Configuration Options

import { ApplicationContext, InstanceScope } from '@vgerbot/ioc';

const context = new ApplicationContext({
    // Default scope
    defaultScope: InstanceScope.SINGLETON,
    
    // Lazy mode
    lazyMode: true
});

πŸ“‹ API Reference

Decorators

  • @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

Core Classes

  • ApplicationContext - Application context and container
  • InstanceScope - Instance scope enumeration
  • Advice - AOP advice type enumeration

Interfaces

  • Aspect - Aspect interface
  • ProceedingAspect - Around advice interface
  • InstanceResolution - Instance resolution interface
  • Evaluator - Expression evaluator interface

🀝 Contributing

Contributions are welcome! Please check the GitHub repository for more information.

πŸ“„ License

This project is licensed under the MIT License.

About

IoC library written in typescript

Resources

License

Stars

Watchers

Forks

Packages

No packages published