import { ActivationError, ActivationItem } from "./ActivationError"; import { IActivationContext, DescriptorMap, ILifetimeManager, ILifetimeSlot, ServiceLocator, IContainerBuilder } from "../typings/interfaces"; import { argumentNotNull, each, prototype } from "./traits"; import { LifetimeManager } from "./LifetimeManager"; import { ContainerBuilder } from "./ContainerBuilder"; /** This object is created once per `Container.resolve` method call and used to * cache dependencies and to track created instances. The activation context * tracks services with `context` activation type. * * @template S The service map used in the activation context, services from * this map are available to resolution. * @template U A set of keys from the service map which can be overridden in * this activation context. */ export class ActivationContext implements IActivationContext { private readonly _container: ServiceLocator; private readonly _contextScope: ILifetimeManager; private _services: DescriptorMap; private readonly _scope: ILifetimeManager[]; /** Creates a new activation context with the specified parameters. * @param containerLifetimeManager the container which starts the activation process * @param services the initial service registrations * @param name the name of the service being activated, this parameter is * used for the debug purpose. * @param service the service to activate, this parameter is used for the * debug purpose. */ constructor(container: ServiceLocator, scope: ILifetimeManager[], services: DescriptorMap, contextScope: ILifetimeManager = new LifetimeManager()) { this._container = container; this._contextScope = contextScope; this._services = services; this._scope = scope; } resolve(name: K, stack: ActivationItem[]): NonNullable; resolve(name: K, stack: ActivationItem[], def: T): NonNullable | T; resolve(name: K, stack: ActivationItem[], def?: T): NonNullable | T | undefined { const service = this._services[name]; if (service !== undefined) { return service.activate( this, stack.concat({ name: name.toString(), descriptor: service.toString() }) ); } else { if (arguments.length > 2) return def; else throw new Error("Service not found"); } } /** * registers services local to the the activation context * * @name{string} the name of the service * @service{string} the service descriptor to register */ private _register(name: K, service: DescriptorMap[K]) { argumentNotNull(name, "name"); const d = this._services[name]; if (d !== undefined && !d.configurable) throw new Error(`Service ${String(name)} can't be overridden`); this._services[name] = service; } scopeSlot(level: number, slotId: string | number): ILifetimeSlot { if (level < 0 || level >= this._scope.length) throw new Error("The scope level is out of range"); return this._scope[level].slot(slotId); } hierarchySlot(slotId: string | number): ILifetimeSlot { return this._scope[this._scope.length - 1].slot(slotId); } selfContainer(): ServiceLocator { return this._container; } createChildContainer(): IContainerBuilder { return new ContainerBuilder(this._services, this._scope); } contextSlot(slotId: string | number): ILifetimeSlot { return this._contextScope.slot(slotId); } withOverrides(overrides: DescriptorMap, action: () => X) { const services = this._services; this._services = prototype(this._services); try { each(overrides, (v, k) => this._register(k, v)); return action(); } finally { this._services = services; } } }