@shellicar/core-di
v1.0.0
Published
A basic dependency injection library
Downloads
203
Maintainers
Readme
@shellicar/core-di
A basic dependency injection library.
Motivation
Coming from .NET I am used to DI frameworks/libraries such as Autofac
, Ninject
, StructureMap
, Unity
, and Microsoft's own DependencyInjection
.
I started using InversifyJS
, and tried out some others along the way, such as diod
.
With TypeScript 5.0 generally available with non-experimental decorators, most DI libraries have not been updated, so I decided to create my own.
Features
My set of features is simple, based on my current usage
- Type-safe registration.
const services = createServiceCollection();
abstract class IAbstract { abstract method(): void; }
abstract class Concrete {}
services.register(IAbstract).to(Concrete);
// ^ Error
- Type-safe resolution.
const provider = services.buildProvider();
const svc = provider.resolve(IMyService);
// ^ IMyService
- Provide factory methods for instantiating classes.
services.register(Redis).to(Redis, x => {
const options = x.resolve(IRedisOptions);
return new Redis({
port: options.port,
host: options.host,
});
});
- Use property injection with decorators for simple dependency definition.
abstract class IDependency {}
class Service implements IService {
@dependsOn(IDependency) private readonly dependency!: IDependency;
}
- Provide multiple implementations for identifiers and provide a
resolveAll
method. - Define instance lifetime with simple builder pattern.
services.register(IAbstract).to(Concrete).singleton();
- Create scopes to allow "per-request" lifetimes.
const services = createServiceCollection();
const provider = services.buildProvider();
using scope = provider.createScope();
- Register classes during a scope
using scope = provider.createScope();
scope.Services.register(IContext).to(Context);
- Override registrations (e.g.: for testing)
import { ok } from 'node:assert/strict';
const services = createServiceCollection({ registrationMode: ResolveMultipleMode.LastRegistered });
services.register(IOptions).to(Options);
// Later
services.register(IOptions).to(MockOptions);
const provider = services.buildProvider();
const options = provider.resolve(IOptions);
ok(options instanceof MockOptions);
- Logging options
class CustomLogger extends ILogger {
public override debug(message?: any, ...optionalParams: any[]): void {
// custom implementation
}
}
// Override default logger
const services1 = createServiceCollection({ logger: new CustomLogger() });
// Override default log level
const services2 = createServiceCollection({ logLevel: LogLevel.Debug });
- Service modules
class IAbstract {}
class Concrete extends IAbstract {}
class MyModule implements IServiceModule {
public registerServices(services: IServiceCollection): void {
services.register(IAbstract).to(Concrete);
}
}
const services = createServiceCollection();
services.registerModules(MyModule);
const provider = services.buildProvider();
const svc = provider.resolve(IAbstract);
Usage
Check the test files for different usage scenarios.
import { dependsOn, createServiceCollection, IServiceModule, type IServiceCollection } from '@shellicar/core-di';
// Define the dependency interface
abstract class IClock {
abstract now(): Date;
}
// And implementation
class DefaultClock implements IClock {
now(): Date {
return new Date();
}
}
// Define your interface
abstract class IDatePrinter {
abstract handle(): string;
}
// And implementation
class DatePrinter implements IDatePrinter {
@dependsOn(IClock) public readonly clock!: IClock;
handle(): string {
return `The time is: ${this.clock.now().toISOString()}`;
}
}
class TimeModule extends IServiceModule {
public registerServices(services: IServiceCollection): void {
services.register(IClock).to(DefaultClock).singleton();
services.register(IDatePrinter).to(DatePrinter).scoped();
}
}
// Register and build provider
const services = createServiceCollection();
services.registerModules([TimeModule]);
const sp = services.buildProvider();
// Optionally create a scope
using scope = sp.createScope();
// Resolve the interface
const svc = scope.resolve(IDatePrinter);
console.log(svc.handle());