@bigbyte/ioc
v0.8.2
Published
<div align="center">
Readme
🔄️ @bigbyte/ioc - Inversion of Control
A lightweight and powerful IoC container for TypeScript with full support for dependency injection and component lifecycle management.
📋 Table of Contents
✨ Features
- 🔄 Automatic dependency injection based on TypeScript metadata
- 🏗️ Component registration with strong typing
- 🎯 Multiple component types (Service, Repository, Controller, etc.)
- 🔍 Intelligent resolution of circular dependencies
- 📊 Event system for dynamic components
- 🛡️ Robust error handling with specific exceptions
- 🎮 Flexible and extensible programmatic API
- 🔧 Granular component configuration
- 📝 Native TypeScript with full type support
🚀 Installation
npm install @bigbyte/ioc🔧 Basic Usage
Manual component registration
import { componentRegistry } from '@bigbyte/ioc';
class DatabaseService {
connect() {
console.log('Connecting to database...');
}
}
class UserService {
constructor(private dbService: DatabaseService) {}
getUsers() {
this.dbService.connect();
return ['user1', 'user2'];
}
}
// Manual registration
componentRegistry.add(DatabaseService, []);
componentRegistry.add(UserService, [DatabaseService]);
// Get instance
const userService = componentRegistry.getByClass(UserService);
console.log(userService.instance.getUsers());Using the Injector
import { Injector, componentRegistry } from '@bigbyte/ioc';
const injector = componentRegistry.getByClass(Injector);
// Register component
injector.add(UserService);
// Check existence
if (injector.has(UserService)) {
const component = injector.get(UserService);
console.log(component?.instance);
}🔍 Detailed API
Component
Each registered component has the following properties:
interface Component {
readonly id: string; // Unique generated ID
readonly name: string; // Class name
readonly class: any; // Class reference
readonly instance: any; // Component instance
readonly options: ComponentOptions; // Configuration
readonly createAt: Date; // Creation date
}The system supports different component types to better organize your architecture:
import { ComponentType } from '@bigbyte/ioc';
enum ComponentType {
MAIN = 'MAIN', // Main component
COMPONENT = 'COMPONENT', // Generic component
SERVICE = 'SERVICE', // Business service
REPOSITORY = 'REPOSITORY', // Data access
CONTROLLER = 'CONTROLLER' // Web controller
}Component configuration
import { ComponentOptions } from '@bigbyte/ioc';
const options: ComponentOptions = {
injectable: true, // Whether it can be injected (default: true)
type: ComponentType.SERVICE // Component type (default: COMPONENT)
};
componentRegistry.add(UserService, [DatabaseService], options);ComponentRegistry
add(Target, dependencies, options?): string
Registers a new component in the container.
const id = componentRegistry.add(
UserService,
[DatabaseService],
{ type: ComponentType.SERVICE }
);getByClass(Target, strict?): Component | undefined
Gets a component by its class.
const component = componentRegistry.getByClass(UserService);
// With strict=false doesn't throw exception if it doesn't exist
const optional = componentRegistry.getByClass(OptionalService, false);getById(id): Component
Gets a component by its unique ID.
const component = componentRegistry.getById('uuid-v4');getByName(name): Component | undefined
Gets a component by its class name.
const component = componentRegistry.getByName('UserService');has(value): boolean
Checks if a component exists (by class or ID).
const exists = componentRegistry.has(UserService);
const existsById = componentRegistry.has('component-id');onComponentByName(name, callback): void
Listens for component availability asynchronously. The callback reacts when the component is added.
componentRegistry.onComponentByName('UserService', (component) => {
console.log('UserService is available:', component.instance);
});Injector
High-level service that is part of the component registry to access it programmatically.
const injector = componentRegistry.getByClass(Injector);
injector.add(MyService); // Add component
const component = injector.get(MyService); // Get component
const exists = injector.has(MyService); // Check existence🏗️ Architecture
Registration Flow
graph TD
A[Class + Dependencies] --> B[ComponentRegistry.add()]
B --> C[Resolve Dependencies]
C --> D[Create Component]
D --> E[Create Instance]
E --> F[Emit Events]
F --> G[Store in Registry]Internal Structure
- ComponentRegistry: Centralized component management
- Component: Instance wrapper with metadata
- Injector: High-level API for programmatic use
- BufferComponent: Event system for dynamic components
⚠️ Error Handling
MissingDependencyError
Thrown when a required dependency is not found in the registry:
try {
componentRegistry.getByClass(NonExistentService);
} catch (error) {
if (error instanceof MissingDependencyError) {
console.log('Missing dependency:', error.message);
}
}NonInjectableComponentError
Thrown when trying to inject a component marked as non-injectable:
// Component marked as non-injectable
componentRegistry.add(UtilityClass, [], { injectable: false });
// This will throw NonInjectableComponentError
componentRegistry.add(ServiceClass, [UtilityClass]);CircularDependencyError
Circular dependencies are automatically detected:
// This will cause a circular dependency error
class ServiceA {
constructor(serviceB: ServiceB) {}
}
class ServiceB {
constructor(serviceA: ServiceA) {}
}🔧 Advanced Examples
Repository Pattern
interface IUserRepository {
findById(id: string): User;
save(user: User): void;
}
class DatabaseUserRepository implements IUserRepository {
constructor(private dbConnection: DatabaseConnection) {}
findById(id: string): User {
// Database implementation
}
save(user: User): void {
// Database implementation
}
}
class UserService {
constructor(private userRepository: IUserRepository) {}
getUser(id: string): User {
return this.userRepository.findById(id);
}
}
// Registration with specific type
componentRegistry.add(DatabaseConnection, [], { type: ComponentType.SERVICE });
componentRegistry.add(DatabaseUserRepository, [DatabaseConnection], {
type: ComponentType.REPOSITORY
});
componentRegistry.add(UserService, [DatabaseUserRepository], {
type: ComponentType.SERVICE
});Event System
// Listen for multiple components
const requiredServices = ['UserService', 'EmailService', 'LoggerService'];
requiredServices.forEach(serviceName => {
componentRegistry.onComponentByName(serviceName, (component) => {
console.log(`✅ ${serviceName} loaded:`, component.createAt);
});
});
// Services can be registered in any order
componentRegistry.add(EmailService, []);
componentRegistry.add(LoggerService, []);
componentRegistry.add(UserService, [EmailService, LoggerService]);Conditional Configuration
class CacheService {
constructor(private isProduction: boolean) {}
}
const isProduction = process.env.NODE_ENV === 'production';
// Registration with specific configuration
componentRegistry.add(CacheService, [], {
injectable: isProduction, // Only injectable in production
type: ComponentType.SERVICE
});
// Conditional usage
if (componentRegistry.has(CacheService)) {
const cache = componentRegistry.getByClass(CacheService);
// Use cache only if available
}📄 License
This project is licensed under the Apache 2.0 license. See the LICENSE file for more details.
Developed with ❤️ by Jose Eduardo Soria Garcia
Part of the BigByte ecosystem
