import { ActivationContext } from "./ActivationContext"; import { ActivationError } from "./ActivationError"; import { RegistrationMap, ContainerKeys, ServiceContainer, ContainerServices, ConfigurableServices, ConfigurableKeys, ServiceLocator, Descriptor} from "./interfaces"; import { LifetimeManager } from "./LifetimeManager"; import { each, isKey } from "./traits"; export class Container implements ServiceContainer { private readonly _services: Partial>>; private readonly _lifetimeManager: LifetimeManager; private readonly _cleanup: (() => void)[]; private _disposed: boolean; constructor(parent?: Container) { this._services = Object.create(parent ? parent._services : null) as typeof this._services; this._cleanup = []; this._services.container = { activate: () => this as ServiceLocator>}; this._services.childContainer = { activate: () => this.createChildContainer() }; this._disposed = false; this._lifetimeManager = new LifetimeManager(); } register>(name: K, service: Descriptor, ConfigurableServices[K]>): void; register>(services: {[k in K]: Descriptor, ConfigurableServices[k]>}): void; register>(nameOrCollection: K | {[k in K]: Descriptor, ConfigurableServices[k]>}, service?: RegistrationMap>[K]) { if (!isKey(nameOrCollection)) { each(nameOrCollection, (v, k) => this.register(k, v)); } else { if (!service) throw new Error("The service parameter must be a descriptor"); this._services[nameOrCollection] = service; } } createChildContainer(): ServiceContainer { return new Container(this); } resolve>(name: K): NonNullable[K]>; resolve>(name: K, def: ContainerServices[K]): ContainerServices[K]; resolve>(name: K, def?: ContainerServices[K]): ContainerServices[K] | undefined { // TODO: add logging // trace.debug("resolve {0}", name); const d = this._services[name]; if (d === undefined) { if (arguments.length > 1) return def; else throw new Error(`Service '${String(name)}' isn't found`); } else { const context = new ActivationContext(this, this._services, String(name), d); try { return d.activate(context); } catch (error) { throw new ActivationError(name.toString(), context.getStack(), error); } } } createLifetime() { return this._lifetimeManager.create(); } destroy() { return this.dispose(); } dispose() { if (this._disposed) return; this._disposed = true; for (const f of this._cleanup) f(); this._lifetimeManager.destroy(); } }