UNPKG

@hermandev/dilite

Version:

Simple and lightweight Dependency Injection container for TypeScript / Next.js apps

88 lines (87 loc) 3 kB
// di/container.ts import { AppError } from "../errors/app-error"; import { globalRegistry, tokenRegistry } from "./registry"; /** Dependency Injection Container */ export class DIContainer { constructor(registry = globalRegistry, tokenMap = tokenRegistry) { this.registry = registry; this.tokenMap = tokenMap; this.singletons = new Map(); this.requestScope = new Map(); } /** Resolve class, handles singleton/request scope */ async resolve(cls) { const registration = this.registry.get(cls); if (!registration) throw new Error(`${cls.name} is not registered as @Injectable`); const { deps, scope } = registration; const map = scope === "singleton" ? this.singletons : this.requestScope; if (map.has(cls)) { return map.get(cls).instance; } const resolvedDeps = await Promise.all(deps.map(async (dep) => { const depClass = this.isClass(dep) ? dep : dep(); return this.resolve(depClass); })); const instance = new cls(...resolvedDeps); if (typeof instance.onInit === "function") { await instance.onInit(); } map.set(cls, { instance, onDestroy: typeof instance.onDestroy === "function" ? () => instance.onDestroy() : undefined, }); return instance; } /** Resolve via string token */ async getInjection(token) { const cls = this.tokenMap.get(token); if (!cls) throw new Error(`No class registered with token '${token}'`); return this.resolve(cls); } /** Reset request-scope objects (called after request ends) */ async endRequestScope() { for (const { onDestroy } of this.requestScope.values()) { if (onDestroy) await onDestroy(); } this.requestScope.clear(); } /** Shutdown global app (e.g. on server close) */ async shutdown() { for (const { onDestroy } of this.singletons.values()) { if (onDestroy) await onDestroy(); } this.singletons.clear(); } isClass(input) { return typeof input === "function" && "prototype" in input; } } export const container = new DIContainer(); export const getInjection = (token) => container.getInjection(token); /** Helper for safe server action execution with error handling */ export async function withContainer(fn) { const c = new DIContainer(); try { const data = await fn(c); return { data }; } catch (err) { if (err instanceof AppError) { return { error: { message: err.message, code: err.code, status: err.status }, }; } return { error: { message: "Internal Server Error", code: "UNKNOWN", status: 500 }, }; } finally { await c.endRequestScope(); } }