Coming from .NET
The container you know, in TypeScript.
@sukujs/di follows Microsoft.Extensions.DependencyInjection closely. If you know
IServiceCollection and IServiceProvider, you know most of it.
| .NET | Suku |
|---|---|
IServiceCollection | ServiceCollection |
services.AddSingleton<IFoo, Foo>() | services.addSingleton(Foo, FooImpl) or singleton(Foo, FooImpl) |
AddScoped, AddTransient | addScoped, addTransient |
TryAddSingleton | tryAddSingleton |
AddKeyedSingleton<T>(key) | addKeyedSingleton(T, key, Impl) |
[FromKeyedServices(key)] | @Inject(T, key) |
services.Replace(...) | services.replace(...) |
BuildServiceProvider(validateScopes, validateOnBuild) | build({ validateScopes, validateOnBuild }) |
GetRequiredService<T>() | provider.get(T) |
GetService<T>() | provider.getOptional(T) |
GetServices<T>() | provider.getAll(T) |
CreateScope() | provider.createScope() |
IDisposable, IAsyncDisposable | dispose(), [Symbol.dispose], [Symbol.asyncDispose] |
IOptions<T> | defineConfig(name, schema) |
ILogger | Logger |
HttpContext | RequestContext |
Middleware (context, next) | Middleware (ctx, next) |
Minimal API app.MapGet("/users/{id}", ...) | route.get('/users/:id', { ... }) |
| Controller class | A @Routes class |
| Model binding and validation | The route's params, query and body schemas |
| Action filters | Middlewares |
Exception filters, UseExceptionHandler | onError |
Interfaces become abstract classes
TypeScript interfaces are erased at compile time, so they cannot be tokens. Use an abstract class in the same role.
export abstract class IClock {
abstract now(): Date;
}
services.addSingleton(IClock, SystemClock);
class Invoices {
constructor(private readonly clock: IClock) {}
}Same rules, same errors
- The last registration for a token wins;
getAllreturns them all in order. - Resolving a scoped service from the root provider throws.
- A singleton that depends on a scoped service is rejected as a captive dependency.
- Disposal runs in reverse order of creation. Instances you registered yourself are left alone.
Routes classes are singletons by default
In ASP.NET Core a controller is created per request. A Suku @Routes class is a singleton
unless you ask otherwise, which avoids an allocation per request. Request-bound services are
usually taken per route instead.
place = route.post('/', {
body: PlaceOrder,
services: { uow: UnitOfWork }, // scoped: one per request
handle: ({ body, uow }) => uow.orders.place(body),
});The composition root
Suku.create plays the part of Program.cs.
const app = await Suku.create({
features: [OrdersFeature],
services: [singleton(IClock, SystemClock)],
configureServices: (services) => services.addScoped(UnitOfWork),
adapter: nodeAdapter(),
});
app.use(requestLogging);
await app.listen(5000);