@organify/di
v1.0.3
Published
A powerful TypeScript dependency injection library with decorators, auto-registration, and advanced features
Maintainers
Readme
@organify/di
A powerful TypeScript dependency injection library with decorators, auto-registration, and advanced features.
Features
- 🎯 Decorator-based DI - Use decorators for clean, readable dependency injection
- 🔄 Auto-registration - Classes are automatically registered when resolved
- 🎭 Multiple scopes - Singleton, Transient, Request, and Session scopes
- 🔍 Advanced resolution - Self, SkipSelf, Optional, Lazy, and Multiple injection
- ✅ Validation & Hooks - Built-in validation and lifecycle hooks
- 🏗️ Modular design - Support for modules and conditional registration
- 🔧 Configuration injection - Inject config values and environment variables
- 🚀 Performance optimized - Efficient resolution with caching
- 🛡️ Type-safe - Full TypeScript support with type inference
Installation
npm install @organify/di reflect-metadataQuick Start
import 'reflect-metadata';
import { Container, Injectable, Inject } from '@organify/di';
// Define your services
@Injectable()
class Logger {
log(message: string) {
console.log(message);
}
}
@Injectable()
class UserService {
constructor(@Inject(Logger) private logger: Logger) {}
getUser(id: string) {
this.logger.log(`Getting user ${id}`);
return { id, name: 'John Doe' };
}
}
// Create container and use
const container = new Container();
const userService = await container.get(UserService);
const user = userService.getUser('123');Core Concepts
Decorators
Class Decorators
// Basic injectable
@Injectable()
class MyService {}
// With scope
@Injectable({ scope: Scope.SINGLETON })
class SingletonService {}
// Convenience scope decorators
@Singleton()
class SingletonService {}
@Transient()
class TransientService {}
@RequestScoped()
class RequestService {}
@SessionScoped()
class SessionService {}
// With priority and conditions
@Priority(10)
@Conditional(container => process.env.NODE_ENV === 'production')
class ConditionalService {}
// Auto-registration control
@AutoRegister(false)
class ManualService {}Injection Decorators
@Injectable()
class MyService {
constructor(
@Inject(Logger) private logger: Logger,
@Optional() @Inject(OptionalService) private optional?: OptionalService,
@Self() @Inject(SelfService) private self: SelfService,
@SkipSelf() @Inject(ParentService) private parent: ParentService,
@Lazy() @Inject(LazyService) private lazy: LazyService,
@Multiple() @Inject(Plugin) private plugins: Plugin[]
) {}
@AutoInject()
private autoInjected: AutoService;
@Config('app.name', 'Default App')
private appName: string;
@Env('NODE_ENV', 'development')
private environment: string;
}Scopes
// Singleton - One instance per container (default)
@Singleton()
class SingletonService {}
// Transient - New instance every time
@Transient()
class TransientService {}
// Request - One instance per request
@RequestScoped()
class RequestService {}
// Session - One instance per session
@SessionScoped()
class SessionService {}Advanced Features
Validation
@Injectable()
@Validate(instance => {
if (!instance.name) return 'Name is required';
if (instance.age < 0) return 'Age must be positive';
return true;
})
class User {
constructor(public name: string, public age: number) {}
}Lifecycle Hooks
@Injectable()
@Hook('afterCreate', async (instance) => {
await instance.initialize();
})
@Hook('beforeDestroy', async (instance) => {
await instance.cleanup();
})
class ServiceWithHooks implements OnInit, OnDestroy {
async onInit() {
console.log('Service initialized');
}
async onDestroy() {
console.log('Service destroyed');
}
}Configuration Injection
@Injectable()
class AppConfig {
@Config('app.port', 3000)
port: number;
@Config('app.host', 'localhost')
host: string;
@Env('NODE_ENV', 'development')
environment: string;
@Env('DEBUG', false)
debug: boolean;
}Multiple Providers
// Register multiple providers for the same token
container.register([
{ provide: 'PLUGIN', useClass: PluginA },
{ provide: 'PLUGIN', useClass: PluginB },
{ provide: 'PLUGIN', useClass: PluginC }
]);
// Inject all providers
@Injectable()
class PluginManager {
constructor(@Multiple() @Inject('PLUGIN') private plugins: Plugin[]) {}
}Container API
// Create container
const container = new Container({
autoRegisterInjectable: true,
strictMode: false,
enableCircularDetection: true
});
// Register providers
container.register([
{ provide: Logger, useClass: Logger },
{ provide: 'API_KEY', useValue: 'secret-key' },
{ provide: Database, useFactory: () => new Database() }
]);
// Register with options
container.register(MyService, {
scope: Scope.SINGLETON,
priority: 10,
condition: container => process.env.NODE_ENV === 'production'
});
// Convenience methods
container.registerSingleton('SERVICE', () => new Service());
container.registerTransient('SERVICE', () => new Service());
container.registerRequest('SERVICE', () => new Service());
// Resolve dependencies
const service = await container.get(MyService);
const services = await container.getAll('PLUGIN');
// Check if provider exists
if (container.has(MyService)) {
// ...
}
// Create child container
const child = container.createChild();
// Get statistics
const stats = container.getStats();
// Validate container
const validation = container.validate();
if (!validation.isValid) {
console.error('Validation errors:', validation.errors);
}
// Cleanup
await container.dispose();Modules
@Module({
providers: [UserService, AuthService],
exports: [UserService]
})
class UserModule {}
@Module({
imports: [UserModule],
providers: [AppService]
})
class AppModule {}Error Handling
try {
const service = await container.get(MyService);
} catch (error) {
if (error instanceof ProviderNotFoundError) {
console.error('Provider not found:', error.message);
} else if (error instanceof CircularDependencyError) {
console.error('Circular dependency detected:', error.message);
} else if (error instanceof ResolutionError) {
console.error('Resolution failed:', error.message);
}
}Advanced Examples
Plugin System
interface Plugin {
name: string;
execute(): void;
}
@Injectable()
class PluginA implements Plugin {
name = 'PluginA';
execute() { console.log('Plugin A executed'); }
}
@Injectable()
class PluginB implements Plugin {
name = 'PluginB';
execute() { console.log('Plugin B executed'); }
}
@Injectable()
class PluginManager {
constructor(@Multiple() @Inject(Plugin) private plugins: Plugin[]) {}
executeAll() {
this.plugins.forEach(plugin => plugin.execute());
}
}Conditional Services
@Injectable()
@Conditional(container => process.env.NODE_ENV === 'production')
class ProductionService {
log(message: string) {
// Production logging
}
}
@Injectable()
@Conditional(container => process.env.NODE_ENV === 'development')
class DevelopmentService {
log(message: string) {
// Development logging
}
}
@Injectable()
class AppService {
constructor(@Inject(Logger) private logger: Logger) {}
// Logger will be resolved based on environment
}Request Scoped Services
@RequestScoped()
class RequestContext {
constructor(
@Inject('REQUEST_ID') private requestId: string,
@Inject('USER') private user: User
) {}
}
@RequestScoped()
class UserService {
constructor(
@Inject(RequestContext) private context: RequestContext
) {}
getCurrentUser() {
return this.context.user;
}
}Best Practices
- Use decorators consistently - Prefer decorators over manual registration
- Keep services focused - Each service should have a single responsibility
- Use appropriate scopes - Choose the right scope for your use case
- Validate dependencies - Use validation to catch issues early
- Handle lifecycle - Implement OnInit and OnDestroy when needed
- Use TypeScript - Leverage type safety for better development experience
Performance Considerations
- Singleton scope provides the best performance for frequently used services
- Request scope is good for per-request state but has memory overhead
- Transient scope creates new instances but has no memory overhead
- Use circular dependency detection in development, disable in production
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
License
MIT
