A lightweight yet powerful dependency injection container for TypeScript applications, inspired by VS Code's instantiation service.
- Hierarchical Service Containers: Create child containers that inherit from parent containers
- Constructor-based Dependency Injection: Automatic injection via parameter decorators
- Lazy Instantiation: Delay service creation until first use for performance
- Cycle Detection: Automatically detect and prevent circular dependencies
- Service Descriptors: Flexible service registration with instantiation metadata
- Type Safety: Full TypeScript support with strict typing
interface ILogger {
log(message: string): void
}
interface IUserService {
getUser(id: string): Promise<User>
}import { createDecorator } from 'di'
const ILogger = createDecorator<ILogger>('logger')
const IUserService = createDecorator<IUserService>('userService')class ConsoleLogger implements ILogger {
log(message: string): void {
console.log(`[LOG] ${message}`)
}
}
class UserService implements IUserService {
constructor(@ILogger private logger: ILogger) {}
async getUser(id: string): Promise<User> {
this.logger.log(`Fetching user ${id}`)
return { id, name: 'John Doe' }
}
}import { ServiceCollection, SyncDescriptor, InstantiationService } from 'di'
const services = new ServiceCollection()
services.set(ILogger, new SyncDescriptor(ConsoleLogger))
services.set(IUserService, new SyncDescriptor(UserService))
const instantiationService = new InstantiationService(services)// Create instance with automatic dependency injection
const userService = instantiationService.createInstance(IUserService)
// Or use accessor pattern
const user = instantiationService.invokeFunction(async (accessor) => {
const userService = accessor.get(IUserService)
return await userService.getUser('123')
})For performance-critical applications, you can enable lazy instantiation:
services.set(IHeavyService, new SyncDescriptor(HeavyService, [], true))Create child containers that inherit services from parent containers:
const childServices = new ServiceCollection()
childServices.set(IChildService, new SyncDescriptor(ChildService))
const childContainer = instantiationService.createChild(childServices)// Create instance with additional arguments
const service = instantiationService.createInstance(MyService, arg1, arg2)
// Create instance from descriptor
const descriptor = new SyncDescriptor(MyService, [arg1])
const service = instantiationService.createInstance(descriptor)Main service container that manages service instances and dependencies.
new InstantiationService(services?: ServiceCollection, strict?: boolean)Methods:
createInstance<T>(descriptor: SyncDescriptor0<T>): TcreateInstance<Ctor>(ctor: Ctor, ...args): InstanceType<Ctor>invokeFunction<R>(fn: (accessor: ServicesAccessor) => R): RcreateChild(services: ServiceCollection): IInstantiationServicedispose(): void
Registry for services and their descriptors.
new ServiceCollection(...entries: [ServiceIdentifier, any][])Methods:
set<T>(id: ServiceIdentifier<T>, instanceOrDescriptor: T | SyncDescriptor<T>)get<T>(id: ServiceIdentifier<T>): T | SyncDescriptor<T> | undefinedhas(id: ServiceIdentifier): boolean
Wrapper for service constructors with instantiation metadata.
new SyncDescriptor<T>(
ctor: new (...args: any[]) => T,
staticArguments?: unknown[],
supportsDelayedInstantiation?: boolean
)Creates a service identifier for dependency injection.
registerSingleton<T>(id: ServiceIdentifier<T>, ctor: new (...args: any[]) => T, supportsDelayedInstantiation?: boolean)
Convenience function for registering singleton services.
# Install dependencies
pnpm install
# Run tests
pnpm test
# Fix linting issues
pnpm fix
# Type check
pnpm tsc --noEmit# Run all tests
pnpm test
# Run specific test file
pnpm test graph.test.ts
# Run in watch mode
pnpm test --watch// services.ts
export const IDatabaseService = createDecorator<IDatabaseService>('databaseService')
export const IAuthService = createDecorator<IAuthService>('authService')
// main.ts
import { InstantiationService } from 'di'
import { services } from './services'
const container = new InstantiationService(services)
// Use in route handlers
app.get('/users/:id', async (req, res) => {
const user = await container.invokeFunction(async (accessor) => {
const authService = accessor.get(IAuthService)
const dbService = accessor.get(IDatabaseService)
await authService.authenticate(req)
return await dbService.getUser(req.params.id)
})
res.json(user)
})import { InstantiationService, ServiceCollection } from 'di'
describe('UserService', () => {
it('should fetch user', async () => {
const services = new ServiceCollection()
services.set(ILogger, new MockLogger())
services.set(IUserService, new SyncDescriptor(UserService))
const container = new InstantiationService(services)
const userService = container.createInstance(IUserService)
const user = await userService.getUser('123')
expect(user.id).toBe('123')
})
})ISC