@smwb/di
v1.0.8
Published
Zero dependencies Dependency Injection
Downloads
88
Readme
@smwb/di - Zero Dependencies Dependency Injection
A lightweight, zero-dependency Dependency Injection container for TypeScript and JavaScript applications.
Features
- Zero external dependencies
- Decorator-based dependency injection (
@Injectableand@Inject) - Support for singleton and transient scopes
- Global container instance for easy access
- Support for binding constants, functions, and classes
- Default binding support
- TypeScript support with full type safety
Installation
npm install @smwb/diUsage
Basic Setup
First, define your services and dependencies using decorators:
import { Injectable, Inject } from '@smwb/di';
import { Database } from './database';
import { Logger } from './logger';
// Define a binding key for your dependencies
const DatabaseKey = Symbol('Database');
const LoggerKey = Symbol('Logger');
@Injectable()
class UserService {
constructor(
@Inject(new BindingKey(DatabaseKey)) private database: Database,
@Inject(new BindingKey(LoggerKey)) private logger: Logger
) {}
getUsers() {
this.logger.log('Fetching users');
return this.database.query('SELECT * FROM users');
}
}
@Injectable({ scope: Scope.TRANSIENT })
class OrderService {
constructor(
@Inject(new BindingKey(DatabaseKey)) private database: Database
) {}
getOrders() {
return this.database.query('SELECT * FROM orders');
}
}Binding Dependencies
Configure your dependency container:
import { container, BindingKey } from '@smwb/di';
import { Database } from './database';
import { Logger } from './logger';
import { UserService } from './user-service';
import { OrderService } from './order-service';
// Bind your dependencies
const DatabaseKey = Symbol('Database');
const LoggerKey = Symbol('Logger');
const UserServiceKey = Symbol('UserService');
const OrderServiceKey = Symbol('OrderService');
container.bind(new BindingKey(DatabaseKey)).toClass(Database);
container.bind(new BindingKey(LoggerKey)).toClass(Logger);
container.bind(new BindingKey(UserServiceKey)).toClass(UserService);
container.bind(new BindingKey(OrderServiceKey)).toClass(OrderService);
// Or bind constants
container.bind(new BindingKey('API_URL')).toConstant('https://api.example.com');
// Or bind functions
container.bind(new BindingKey('randomNumber')).toFunction(() => Math.random());Resolving Dependencies
Get instances of your services:
import { container, BindingKey } from '@smwb/di';
import { UserService } from './user-service';
const UserServiceKey = Symbol('UserService');
// Resolve dependencies
const userService = container.resolve(new BindingKey(UserServiceKey));
const users = userService.getUsers();
// For constants and functions
const apiUrl = container.resolve(new BindingKey('API_URL'));
const randomNumber = container.resolve(new BindingKey('randomNumber'));Default Bindings
You can also use default bindings to simplify your code:
import { Injectable, Inject } from '@smwb/di';
// Define binding keys
const DatabaseKey = Symbol('Database');
const UserServiceKey = Symbol('UserService');
@Injectable()
class Database {
connect() {
// Database connection logic
}
}
@Injectable()
class UserService {
constructor(
@Inject(new BindingKey(DatabaseKey, Database)) private database: Database
) {}
getUsers() {
return this.database.connect();
}
}
// No need to explicitly bind Database, it will use the default
const userService = container.resolve(
new BindingKey(UserServiceKey, UserService)
);API
container
Global instance of DIContainer for managing dependencies.
DIContainer
Main container class with the following methods:
bind<V>(key: BindingKey<V>)- Binds a key to a new binding instanceresolve<V>(key: BindingKey<V>, options?: { optional?: boolean })- Resolves a dependency by its key. If optional is true and no binding is found, returns undefined instead of throwing an error.clear()- Clears all cached instances
BindingKey<V>
Represents a key for binding dependencies:
new BindingKey<V>(key: symbol, defaultBinding?: Newable<V> | V)@Injectable(options?)
Class decorator to mark a class as injectable:
@Injectable({ scope: Scope.SINGLETON }) // or Scope.TRANSIENT
class MyService {}@Inject(binding, options?)
Parameter decorator to inject dependencies:
// Define binding keys at the top of your file
const DependencyKey = Symbol('dep');
constructor(@Inject(new BindingKey(DependencyKey)) dependency: DependencyType)Options can include:
optional: boolean- If true, the dependency is optional and won't throw an error if not found
Example with optional dependency:
const OptionalDependencyKey = Symbol('optional-dep');
constructor(
@Inject(new BindingKey(OptionalDependencyKey), { optional: true })
optionalDependency: OptionalDependencyType | undefined
)When resolving dependencies directly:
const optionalService = container.resolve(
new BindingKey(OptionalServiceKey),
{ optional: true }
);
// optionalService will be undefined if no binding is found
## Scopes
- `Scope.SINGLETON` - Single instance shared across the application (default)
- `Scope.TRANSIENT` - New instance created each time it's resolved
## Testing
Run tests with:
```bash
npm testRun tests with coverage:
npm run test:coverageLicense
MIT
