@cw-base/di
v0.0.1
Published
Decorator destekli discovery (otomatik keşif) ve scoped lifecycle (isteğe bağlı yaşam döngüsü) sunan dependency injection konteyneri.
Readme
@cw-base/di
Dekoratör tabanlı discovery (otomatik keşif) ve lifecycle (yaşam döngüsü) yönetimiyle TypeScript için hafif dependency injection (bağımlılık enjeksiyonu) kutuphanesi.
Özellikler
@Servicedekoratörü ile otomatik provider (sağlayıcı) kaydı.- Constructor injection (kurucu enjeksiyonu) +
@Injectile özel token desteği. - Lifecycle seçenekleri:
Singleton,Scoped,Transient. Container.dispose()veScope.dispose()ile kaynak temizliği (async destekli).@Controllerve HTTP kısayol dekoratörleri (@Get,@Post, …) ile rota metadata’sı.@Middlewaredekoratörü sayesinde middleware sınıflarını otomatik kaydetme.- Controller ve middleware metadata’larını
defaultControllerRegistryvedefaultMiddlewareRegistryüzerinden dışa aktarır.
Kurulum
pnpm add @cw-base/di reflect-metadata
reflect-metadatayalnızca bir kez import edilmeli:import "reflect-metadata";
Temel Kullanım
Servis Tanımı
// services/user-service.ts
import { Service, Lifecycle } from "@cw-base/di";
@Service({ lifecycle: Lifecycle.Singleton })
export class UserService {
async findAll() {
return [{ id: 1, name: "Ada Lovelace" }];
}
}Dependency Enjeksiyonu
// services/report-service.ts
import { Service } from "@cw-base/di";
import { UserService } from "./user-service";
@Service()
export class ReportService {
constructor(private readonly userService: UserService) {}
async buildReport() {
const users = await this.userService.findAll();
return { count: users.length, users };
}
}Container Bootstrap
import "reflect-metadata";
import express from "express";
import { bootstrapContainer } from "@cw-base/di";
async function main() {
const container = await bootstrapContainer({
discover: true // @Service, @Controller ve @Middleware dekoratörlü sınıfları kaydeder
});
const report = await container.resolve(ReportService).buildReport();
console.log(report);
}
main();bootstrapContainer çağrısı aynı anda birden fazla yapılırsa hata verir; mevcut konteyneri yenilemek için force: true kullanılabilir.
await bootstrapContainer({ discover: true }); // İlk çağrı
await bootstrapContainer({ force: true, discover: true }); // Önceki konteyner dispose edilirKüresel konteynerin hazır olup olmadığını kontrol etmek için:
import { isContainerBootstrapped } from "@cw-base/di";
if (!isContainerBootstrapped()) {
await bootstrapContainer();
}Konteyneri manuel kapatmak için:
await container.dispose(); // Singleton + transient disposable örnekleri temizlenirveya global örneği sıfırlamak için:
await resetContainer();Scoped Lifecycle
Scoped servisler bir istek bağlamında tek örnek üretir:
@Service({ lifecycle: Lifecycle.Scoped })
export class RequestContext {
constructor(private readonly requestId: string) {}
}
const scope = container.createScope();
const context = scope.resolve(RequestContext);
await scope.dispose(); // Scoped/transient örneklerin dispose metotlarını tetikler@Controller ile Route Metadata’sı
import { Controller, Get, Post, Inject, Lifecycle } from "@cw-base/di";
import { AuthGuardToken, RateLimitToken } from "../middlewares";
import { UserService } from "../services/user-service";
@Controller({
prefix: "/users",
middlewares: [AuthGuardToken], // controller seviyesindeki middleware token’ları
lifecycle: Lifecycle.Singleton
})
export class UserController {
constructor(private readonly userService: UserService) {}
@Get({ path: "/", middlewares: [RateLimitToken], name: "user.list" })
async list(req: Request, res: Response) {
const users = await this.userService.findAll();
res.json(users);
}
@Post({ path: "/", statusCode: 201 })
async create(req: Request, res: Response) {
const created = await this.userService.create(req.body);
res.status(201).json(created);
}
}Controller metadata’sına erişmek için:
import { defaultControllerRegistry } from "@cw-base/di";
const controllers = defaultControllerRegistry.list();
controllers.forEach((definition) => {
console.log(definition.prefix, definition.routes.length);
});@Middleware ile Otomatik Middleware Kaydı
import { Middleware, Lifecycle } from "@cw-base/di";
export const AUTH_GUARD_TOKEN = Symbol("AuthGuard");
@Middleware({ token: AUTH_GUARD_TOKEN, lifecycle: Lifecycle.Transient })
export class AuthGuardMiddleware {
handle(req: Request, res: Response, next: NextFunction) {
if (!req.headers.authorization) {
res.status(401).end();
return;
}
next();
}
}Varsayılan handler metodu handle; farklı isim kullanmak için handlerMethod belirt:
@Middleware({ handlerMethod: "execute" })
export class LoggerMiddleware {
execute(req: Request, _res: Response, next: NextFunction) {
console.log(req.method, req.path);
next();
}
}Middleware metadata’sına erişmek için:
import { defaultMiddlewareRegistry } from "@cw-base/di";
const middlewares = defaultMiddlewareRegistry.list();
middlewares.forEach((definition) => {
console.log(definition.token, definition.handlerMethod);
});Express Adaptörü Örneği
Aşağıdaki minimal örnek registry verilerini kullanarak Express rotalarını bağlar.
import express from "express";
import {
bootstrapContainer,
defaultControllerRegistry,
defaultMiddlewareRegistry
} from "@cw-base/di";
const app = express();
async function registerRoutes() {
const container = await bootstrapContainer({ discover: true });
for (const controller of defaultControllerRegistry.list()) {
const instance = container.resolve(controller.token);
for (const route of controller.routes) {
const controllerMiddleware = controller.middlewares.map((token) =>
container.resolve(token)
);
const routeMiddleware = route.middlewares.map((token) => container.resolve(token));
const handlers = [...controllerMiddleware, ...routeMiddleware];
const method = route.method.toLowerCase() as keyof express.Application;
(app[method] as express.Application[keyof express.Application])(
controller.prefix + route.path,
...handlers,
async (req, res, next) => {
try {
const result = await instance[route.handlerName](req, res, next);
if (result !== undefined && !res.headersSent) {
res.json(result);
}
} catch (error) {
next(error);
}
}
);
}
}
return container;
}
registerRoutes().then(() => {
app.listen(3000, () => console.log("API listening on http://localhost:3000"));
});Not: Bu adaptör örneği sadece fikir vermek içindir. Gerçek projede hataların loglanması, response status kodu yönetimi ve
@cw-base/log/@cw-base/eventsile entegrasyon planlanmalıdır (TODO kayıtları mevcut).
Dispose ve Lifecycle Olayları
Container.dispose()çocuk konteynerleri, scoped context’leri, singleton ve DI tarafından üretilen transient disposable örnekleri sırayla temizler.Scope.dispose()scope içindeki scoped/transient disposable örnekleri temizler.- Hem container hem scope dispose süreci async çalışır;
dispose()metoduPromisedöner. - Hata oluşursa logger
errorçağrıları ile kaydedilir ve hata tekrar fırlatılır (tek hataError, birden fazla hataAggregateError). - Transient disposable örnekleri temizlenirse logger
warnçağrısı ile bilgilendirilir (TODO: @cw-base/events ile event yayını).
Gelecek Adımlar / TODO
@cw-base/logpaketinin hazır olmasıyla birlikte varsayılan logger’ı buraya yönlendirmek.@cw-base/eventsile container lifecycle ve controller kayıt sürecini olay olarak yayınlamak.- Express adaptörünü core paketine taşıyarak burada sadece metadata’ları expose etmek.
Lisans
MIT
