import { ActivationContext } from "./ActivationContext"; import { ContainerBuilder } from "./ContainerBuilder"; import { LifetimeManager, emptySlot } from "./LifetimeManager"; import { DescriptorMap, IContainerBuilder, IDestroyable, ILifetimeManager, ILifetimeSlot, ServiceLocator } from "../typings/interfaces"; let nextId = 1; export class Container implements ServiceLocator, IDestroyable { private readonly _services: DescriptorMap; private readonly _scope: ILifetimeManager[]; private readonly _containerId = `container-${nextId++}`; private readonly _slot: ILifetimeSlot; private _disposed: boolean; constructor(services: DescriptorMap, parentScope: ILifetimeManager[]) { this._services = services; this._disposed = false; this._scope = parentScope.concat(new LifetimeManager()); // If this container is created inside the parent container scope, // allocated lifetime slot this._slot = parentScope.length ? parentScope[parentScope.length - 1].slot(this._containerId) : emptySlot(); // store the container reference in the lifetime slot this._slot.store(this); } private _assertNotDestroyed() { if (this._disposed) throw new Error("The container is destroyed"); } createChildBuilder(): IContainerBuilder { this._assertNotDestroyed(); return new ContainerBuilder(this._services, this._scope); } resolve(name: K): NonNullable; resolve(name: K, def: T): NonNullable | T; resolve(name: K, def?: T) { this._assertNotDestroyed(); const context = new ActivationContext(this, this._scope, this._services); return arguments.length === 1 ? context.resolve(name, []) : context.resolve(name, [], def); } destroy() { if (this._disposed) return; this._disposed = true; // destroy own scope this._scope[this._scope.length - 1].destroy(); // release lifetime slot if the container is destroyed before the parent // container. If this container is destroyed during the parent container // cleanup procedure this call will have no effect. this._slot.remove(); } }