export type Provider = (manager: DependencyManager) => T; export type ProviderGroup = { [key: string]: Provider | ProviderGroup; }; export class DependencyManager { private declarations = new Map(); private instances = new Map(); private resolving = new Set(); register(dependency: Provider): void; register(module: ProviderGroup): void; register(dependencyOrModule: Provider | ProviderGroup): void; register(dependencyOrModule: Provider | ProviderGroup) { if (this.declarations.has(dependencyOrModule)) { throw new Error('This dependency has already been registred'); } if (typeof dependencyOrModule === 'function') { this.declarations.set(dependencyOrModule, dependencyOrModule); return; } const module = dependencyOrModule; for (const key in module) { this.register(module[key]); } } resolve(dependency: Provider): T { if (!this.declarations.has(dependency)) { throw new Error('This dependency has not been registred'); } if (this.instances.has(dependency)) { return this.instances.get(dependency) as T; } if (this.resolving.has(dependency)) { throw new Error('Circular dependency detected'); } this.resolving.add(dependency); const instance = this.instanciate(dependency); this.resolving.delete(dependency); return instance; } private instanciate(dependency: Provider): T { const declaration = this.declarations.get(dependency) as Provider; const instance = declaration(this); this.instances.set(dependency, instance); return instance; } }