Dependency Injection for TypeScript
- TypeScript 100%
|
|
||
|---|---|---|
| .forgejo/workflows | ||
| src | ||
| tests | ||
| .gitignore | ||
| bun.lock | ||
| bunfig.toml | ||
| LICENSE | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
di4ts
A lightweight, Bun-native dependency injection library for TypeScript.
Features
- Simple API: Both decorator and fluent APIs available
- Singleton Lifecycle: All services are singletons by default
- Async Support: First-class async initialization support
- Circular Dependency Handling: Automatic proxy-based circular dependency resolution
- Type-Safe: Full TypeScript support with type inference
- Zero Config: Works out of the box with Bun
Installation
bun add di4ts
Quick Start
Using Decorators
import 'reflect-metadata';
import { Container, injectable, inject, createToken } from 'di4ts';
// Create tokens for interfaces
const ILogger = createToken<Logger>('ILogger');
@injectable()
class Logger {
log(msg: string) {
console.log(msg);
}
}
@injectable()
class UserService {
constructor(@inject(ILogger) private logger: Logger) {}
getUser(id: string) {
this.logger.log(`Getting user ${id}`);
return { id, name: 'John' };
}
}
// Configure container
const container = new Container();
container.bind(ILogger).to(Logger);
// Resolve dependencies
const userService = container.get(UserService);
userService.getUser('123');
Using Fluent API
import 'reflect-metadata';
import { Container, createToken } from 'di4ts';
interface IDatabase {
query(sql: string): Promise<any[]>;
}
const IDatabase = createToken<IDatabase>('IDatabase');
const container = new Container();
// Bind to class
container.bind(IDatabase).to(Database);
// Bind to value
container.bind(IConfig).toValue({ connectionString: '...' });
// Bind to factory
container.bind(IDatabase).toFactory((c) => {
const config = c.get(IConfig);
return new Database(config.connectionString);
});
// Resolve
const db = container.get(IDatabase);
API Reference
Container
The main DI container that manages service registrations and resolution.
const container = new Container({
parent: parentContainer, // Optional parent for hierarchical DI
detectCircularDependencies: true, // Enable circular dependency detection
});
Methods
| Method | Description |
|---|---|
bind<T>(token) |
Create a binding builder for a token |
get<T>(token) |
Resolve a dependency synchronously |
getAsync<T>(token) |
Resolve a dependency asynchronously |
resolve<T>(Class) |
Auto-resolve a class without registration |
resolveAsync<T>(Class) |
Auto-resolve a class asynchronously |
has<T>(token) |
Check if a token is registered |
init() |
Initialize all async dependencies |
createChild() |
Create a child container |
clear() |
Clear all registrations |
Binding Builder
Fluent API for configuring bindings.
container.bind(IToken)
.to(Class) // Bind to class constructor
.toValue(instance) // Bind to existing value
.toFactory(fn) // Bind to factory function
.toAsyncFactory(fn) // Bind to async factory
Decorators
| Decorator | Description |
|---|---|
@injectable() |
Marks a class as injectable |
@singleton() |
Alias for @injectable() |
@inject(token) |
Specifies injection token for parameter |
Token Creation
// Create a type-safe token
const ILogger = createToken<Logger>('ILogger');
// Tokens can be strings, symbols, or classes
const token1 = 'my-service';
const token2 = Symbol('my-service');
const token3 = MyClass;
Async Initialization
const container = new Container();
// Register async factory
container.bind(IDatabase).toAsyncFactory(async () => {
const db = new Database();
await db.connect();
return db;
});
// Initialize all async dependencies
await container.init();
// Now sync access works
const db = container.get(IDatabase);
Or use getAsync directly:
const db = await container.getAsync(IDatabase);
Circular Dependencies
di4ts handles circular dependencies automatically using proxies:
@injectable()
class ServiceA {
constructor(@inject(IServiceB) public b: IServiceB) {}
}
@injectable()
class ServiceB {
constructor(@inject(IServiceA) public a: IServiceA) {}
}
container.bind(IServiceA).to(ServiceA);
container.bind(IServiceB).to(ServiceB);
const a = container.get(IServiceA);
// a.b is a proxy that resolves to ServiceB
// a.b.a is a proxy that resolves back to ServiceA
Hierarchical DI
Create child containers that inherit from parent containers:
const parent = new Container();
parent.bind(IService).to(Service);
const child = parent.createChild();
child.bind(IService).to(AlternativeService); // Override parent binding
// Child can access parent bindings
// But parent cannot access child bindings
Error Handling
import {
DependencyNotFoundError,
CircularDependencyError,
AsyncDependencyError,
NotInjectableError,
ResolutionError,
} from 'di4ts';
try {
container.get(IUnknownToken);
} catch (e) {
if (e instanceof DependencyNotFoundError) {
console.log('Token not found:', e.token);
}
}
Requirements
- Bun 1.0+
- TypeScript 5.0+
reflect-metadata(automatically imported)
License
MIT