import { ActivationError } from "./ActivationError"; import { Descriptor, ILifetime, IActivationContext, DescriptorMap, ILifetimeManager } from "./interfaces"; import { argumentNotNull, prototype } from "./traits"; export interface ActivationContextInfo { name: string; service: string; } let nextId = 1; /** 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 _cache: Record; private readonly _services: DescriptorMap; private readonly _name: string; private readonly _service: Descriptor; private readonly _containerLifetimeManager: ILifetimeManager; private readonly _parent: ActivationContext | undefined; /** 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(containerLifetimeManager: ILifetimeManager, services: DescriptorMap, name: string, service: Descriptor, cache = {}) { this._name = name; this._service = service; this._cache = cache; this._services = services; this._containerLifetimeManager = containerLifetimeManager; } /** the name of the current resolving dependency */ getName() { return this._name; } createContainerLifetime() { return this._containerLifetimeManager.create(); } resolve(name: K): NonNullable; resolve(name: K, def: T): NonNullable | T; resolve(name: K, def?: T): NonNullable | T | undefined { const d = this._services[name]; if (d !== undefined) { return this.activate(d, name.toString()); } else { if (arguments.length > 1) return def; else throw new Error(`Service ${String(name)} not found`); } } /** * registers services local to the the activation context * * @name{string} the name of the service * @service{string} the service descriptor to register */ 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; } createLifetime(): ILifetime { const id = nextId++; return { initialize() { }, has: () => id in this._cache, get: () => { const v = this._cache[id] as T; if (v === undefined || v === null) throw new Error("The value isn't present in the activation context"); return v; }, store: item => { this._cache[id] = item; } }; } activate(d: Descriptor, name: string) { // TODO: add logging // if (trace.isLogEnabled()) // trace.log("enter {0} {1}", name, d); const ctx = new ActivationContext( this._containerLifetimeManager, d.hasOverrides ? prototype(this._services) : this._services, name, d, this._cache ); const v = d.activate(ctx); // if (trace.isLogEnabled()) // trace.log(`leave ${name}`); return v; } getStack(): ActivationContextInfo[] { const stack = [{ name: this._name, service: this._service.toString() }]; return this._parent ? stack.concat(this._parent.getStack()) : stack; } fail(innerException: unknown): never { throw new ActivationError(this._name, this.getStack(), innerException); } }