Schnellstart
Beginne mit zwei gewöhnlichen Klassen und einer expliziten Registrierungskette. Request-Scopes kommen hinzu, sobald der grundlegende Graph steht.
Installieren
pnpm add @inferdi/inferdinpm install @inferdi/inferdiyarn add @inferdi/inferdiDen Graphen aufbauen
import { Container } from '@inferdi/inferdi'
export class Logger {
info(message: string) {
console.info(message)
}
}
export class UserService {
constructor(private readonly logger: Logger) {}
find(id: string) {
this.logger.info(`user=${id}`)
return { id }
}
}
export const root = new Container()
.registerClass('logger', Logger, [])
.registerClass('users', UserService, ['logger'])
export const users = root.get('users')UserService importiert InferDI nicht. Der Kompositionscode wählt Logger, benennt beide Registrierungen und legt die Reihenfolge der Konstruktorargumente fest. Der zurückgegebene Typ von root kennt nun beide Services.
Änderst du den Konstruktor, ohne den Graphen anzupassen, schlägt die Registrierung direkt beim Zusammenstellen der Anwendung fehl:
import { Container } from '@inferdi/inferdi'
class Logger {
info(message: string) {}
}
class UserService {
constructor(readonly logger: Logger, readonly region: string) {}
}
new Container()
.registerClass('logger', Logger, [])
.registerClass('users', UserService, ['logger']) Genau das bedeutet, den Graphenzustand im Typ abzubilden: Registrierungen verfeinern den Containertyp, und spätere Operationen müssen zum bereits deklarierten Graphen passen.
Services auflösen
root.get('users') liefert synchron einen UserService. Die Standardlebensdauer ist Singleton; wiederholte Aufrufe liefern daher dieselbe gecachte Instanz.
Anfragedaten benötigen eine kürzere Lebensdauer. Deklariere sie als Scope-Eingabe und übergib sie beim Öffnen eines Kind-Scopes:
import { Container } from '@inferdi/inferdi'
type RequestContext = {
requestId: string
}
class RequestLog {
constructor(readonly request: RequestContext) {}
dispose() {
console.info(`closed ${this.request.requestId}`)
}
}
const root = new Container()
.declareScopeInputs<{ request: RequestContext }>()
.registerClass('requestLog', RequestLog, ['request'], 'scoped')
export async function handle(request: RequestContext) {
const scope = root.createScope({ request })
try {
return scope.get('requestLog')
} finally {
await scope.dispose()
}
}Der finally-Block schließt das Kind auch bei einem Fehler. scope.dispose() gibt den scoped RequestLog frei; der übergebene Wert request bleibt in der Verantwortung der Anwendung. Unterstützt deine TypeScript-Toolchain Explicit Resource Management, kannst du stattdessen await using verwenden.
Lebensdauern wählen
| Lebensdauer | Instanzregel | Cache gehört | Freigabe durch |
|---|---|---|---|
singleton | eine Instanz je Registrierungsbesitzer | Root oder Registrierungsbesitzer | diesen Container |
scoped | eine Instanz je auflösendem Scope | Kind-Scope | diesen Scope |
transient | eine Instanz je Auflösung | kein Cache | Aufrufer |
Werte aus registerValue, .override() und Scope-Eingaben bleiben ebenfalls anwendungseigen. Ein Singleton darf nicht direkt von einem scoped oder transienten Service abhängen. InferDI weist diese deklarierte Beziehung im Typsystem zurück und prüft sie im Standardmodus zusätzlich zur Laufzeit.
Wie es weitergeht
Warum InferDI? erläutert die Entwurfsentscheidungen, Typsicherheit die Graphenprüfungen. Scopes und Freigabe erklärt die Ressourcenverantwortung; Framework-Adapter verbinden Scopes mit dem Lebenszyklus der Anwendung.
