Package Exports
- @lexms/di-container
- @lexms/di-container/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@lexms/di-container) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
@lexms/di-container
A lightweight, TypeScript-first dependency injection container with support for both synchronous and asynchronous service resolution.
Features
- ๐ Lightweight: Minimal overhead with no external dependencies (except reflect-metadata for decorators)
- ๐ Sync & Async: Support for both synchronous and asynchronous service factories
- ๐ก๏ธ Type Safe: Full TypeScript support with comprehensive type definitions
- ๐ Lifetime Management: Singleton and transient service lifetimes
- ๐ฏ Flexible Tokens: Register services using classes, strings, or symbols
- ๐ซ Circular Dependency Detection: Automatic detection and prevention of circular dependencies
- ๐จ Decorator Support: Optional decorator-based dependency injection
- ๐งน Automatic Cleanup: Built-in disposal pattern for resource cleanup
- ๐ Logging: Optional logging for debugging and monitoring
- โก Performance Monitoring: Built-in performance tracking and metrics collection
Installation
pnpm add @lexms/di-containerFor decorator support, also install:
pnpm add reflect-metadataQuick Start
import { DIContainer, LifetimeScope } from '@lexms/di-container';
// Create a container
const container = new DIContainer();
// Define services
class DatabaseService {
connect() {
console.log('Connected to database');
}
}
class UserService {
constructor(private db: DatabaseService) {}
getUsers() {
this.db.connect();
return ['user1', 'user2'];
}
}
// Register services
container.registerSingleton(DatabaseService, () => new DatabaseService());
container.registerSingleton(UserService, () =>
new UserService(container.resolve(DatabaseService))
);
// Resolve and use
const userService = container.resolve(UserService);
const users = userService.getUsers();API Reference
DIContainer
Constructor
const container = new DIContainer(options?: DIContainerOptions);Options:
enableLogging?: boolean- Enable debug logging (default: false)logPrefix?: string- Custom log prefix (default: 'DIContainer')enablePerformanceMonitoring?: boolean- Enable performance tracking (default: false)
Registration Methods
register<T>(token, factory, scope?)
Register a service with explicit lifetime scope.
container.register(MyService, () => new MyService(), LifetimeScope.SINGLETON);registerSingleton<T>(token, factory)
Register a singleton service (same instance returned on every resolve).
container.registerSingleton(MyService, () => new MyService());registerTransient<T>(token, factory)
Register a transient service (new instance returned on every resolve).
container.registerTransient(MyService, () => new MyService());registerInstance<T>(token, instance)
Register an existing instance.
const config = { apiUrl: 'https://api.example.com' };
container.registerInstance('config', config);Resolution Methods
resolve<T>(token)
Synchronously resolve a service.
const service = container.resolve(MyService);resolveAsync<T>(token)
Asynchronously resolve a service (required for async factories).
const service = await container.resolveAsync(MyAsyncService);Utility Methods
has(token): boolean
Check if a service is registered.
if (container.has(MyService)) {
// Service is registered
}getRegisteredTokens(): (string | symbol)[]
Get all registered service tokens.
const tokens = container.getRegisteredTokens();clear(): void
Remove all service registrations.
container.clear();dispose(): Promise<void>
Dispose the container and call dispose() on all disposable services.
await container.dispose();getPerformanceStats(): ContainerPerformanceStats
Get comprehensive performance statistics for the container.
const stats = container.getPerformanceStats();
console.log(`Total resolutions: ${stats.totalResolutions}`);
console.log(`Average time: ${stats.averageResolutionTime}ms`);getServiceMetrics(token?): ServicePerformanceMetrics[]
Get performance metrics for specific services or all services.
// Get metrics for all services
const allMetrics = container.getServiceMetrics();
// Get metrics for specific service
const serviceMetrics = container.getServiceMetrics(MyService);resetPerformanceStats(): void
Reset all performance statistics.
container.resetPerformanceStats();Advanced Usage
Async Services
class AsyncDatabaseService {
async connect() {
// Async connection logic
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('Connected to database');
}
}
// Register with async factory
container.registerSingleton(AsyncDatabaseService, async () => {
const service = new AsyncDatabaseService();
await service.connect();
return service;
});
// Must use resolveAsync for async factories
const dbService = await container.resolveAsync(AsyncDatabaseService);String and Symbol Tokens
// String tokens
container.registerInstance('apiUrl', 'https://api.example.com');
const apiUrl = container.resolve<string>('apiUrl');
// Symbol tokens
const DATABASE_CONFIG = Symbol('DatabaseConfig');
container.registerInstance(DATABASE_CONFIG, { host: 'localhost', port: 5432 });
const dbConfig = container.resolve<{ host: string; port: number }>(DATABASE_CONFIG);Performance Monitoring
Enable performance monitoring to track service resolution times and identify bottlenecks.
const container = new DIContainer({
enablePerformanceMonitoring: true,
enableLogging: true
});
class FastService {
process() { return 'fast'; }
}
class SlowService {
process() {
// Simulate slow operation
const start = Date.now();
while (Date.now() - start < 100) {}
return 'slow';
}
}
container.registerSingleton(FastService, () => new FastService());
container.registerTransient(SlowService, () => new SlowService());
// Resolve services multiple times
container.resolve(FastService);
container.resolve(FastService); // Cached singleton
container.resolve(SlowService);
container.resolve(SlowService); // New instance
// Get performance statistics
const stats = container.getPerformanceStats();
console.log(`Total resolutions: ${stats.totalResolutions}`);
console.log(`Average resolution time: ${stats.averageResolutionTime}ms`);
console.log(`Slowest services:`, stats.slowestServices);
// Get service-specific metrics
const metrics = container.getServiceMetrics();
metrics.forEach(metric => {
console.log(`${metric.token}: ${metric.averageTime}ms avg (${metric.totalResolutions} calls)`);
});
// Reset statistics
container.resetPerformanceStats();Disposable Services
Services that implement a dispose() method will be automatically disposed when the container is disposed.
class ResourceService {
private connection: any;
constructor() {
this.connection = createConnection();
}
async dispose() {
await this.connection.close();
console.log('Resources cleaned up');
}
}
container.registerSingleton(ResourceService, () => new ResourceService());
// Later...
await container.dispose(); // Automatically calls ResourceService.dispose()Decorator Support (Optional)
First, import reflect-metadata at the top of your main file:
import 'reflect-metadata';
import { Injectable, Inject, autoRegister } from '@lexms/di-container';
@Injectable
class DatabaseService {
connect() {
console.log('Connected to database');
}
}
@Injectable
class UserService {
constructor(
private db: DatabaseService,
@Inject('config') private config: any
) {}
}
// Auto-register decorated classes
autoRegister(DatabaseService);
autoRegister(UserService);Error Handling
The container provides specific error types for different failure scenarios:
import {
ServiceNotFoundError,
CircularDependencyError,
DIContainerError
} from '@lexms/di-container';
try {
const service = container.resolve(UnregisteredService);
} catch (error) {
if (error instanceof ServiceNotFoundError) {
console.log('Service not found');
} else if (error instanceof CircularDependencyError) {
console.log('Circular dependency detected');
}
}Best Practices
1. Use Interface-based Design
interface IUserRepository {
getUsers(): User[];
}
class DatabaseUserRepository implements IUserRepository {
getUsers(): User[] {
// Database implementation
return [];
}
}
class MockUserRepository implements IUserRepository {
getUsers(): User[] {
// Mock implementation
return [{ id: 1, name: 'Test User' }];
}
}
// Register based on environment
const repository = process.env.NODE_ENV === 'test'
? new MockUserRepository()
: new DatabaseUserRepository();
container.registerInstance('IUserRepository', repository);2. Factory Functions for Complex Dependencies
container.registerSingleton(UserService, () => {
const repository = container.resolve<IUserRepository>('IUserRepository');
const logger = container.resolve<ILogger>('ILogger');
const config = container.resolve<Config>('config');
return new UserService(repository, logger, config);
});3. Container Composition
class DatabaseModule {
static register(container: DIContainer) {
container.registerSingleton(DatabaseService, () => new DatabaseService());
container.registerSingleton(UserRepository, () =>
new UserRepository(container.resolve(DatabaseService))
);
}
}
class ServiceModule {
static register(container: DIContainer) {
container.registerSingleton(UserService, () =>
new UserService(container.resolve(UserRepository))
);
}
}
// Register all modules
DatabaseModule.register(container);
ServiceModule.register(container);Development
Scripts
# Install dependencies
pnpm install
# Build the project
pnpm run build
# Run tests
pnpm test
# Run tests in watch mode
pnpm run test:watch
# Run linting
pnpm run lint
# Fix linting issues
pnpm run lint:fix
# Clean build outputs
pnpm run cleanLicense
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.