|
|
import { Descriptor, ILifetime, RegistrationMap, LifetimeContainer, ConfigurableKeys } from "./interfaces";
|
|
|
import { argumentNotNull } from "./traits";
|
|
|
|
|
|
export interface ActivationContextInfo {
|
|
|
name: string;
|
|
|
|
|
|
service: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
let nextId = 1;
|
|
|
|
|
|
/** This class 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.
|
|
|
*/
|
|
|
export class ActivationContext<S extends object> {
|
|
|
private _cache: { [K: string]: unknown };
|
|
|
|
|
|
private _services: Partial<RegistrationMap<S>>;
|
|
|
|
|
|
private _name: string;
|
|
|
|
|
|
private _service: Descriptor<S, unknown>;
|
|
|
|
|
|
private readonly _containerLifetimeManager: LifetimeContainer;
|
|
|
|
|
|
private _parent: ActivationContext<S> | 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: LifetimeContainer, services: Partial<RegistrationMap<S>>, name: string, service: Descriptor<S, unknown>) {
|
|
|
this._name = name;
|
|
|
this._service = service;
|
|
|
this._cache = {};
|
|
|
this._services = services;
|
|
|
this._containerLifetimeManager = containerLifetimeManager;
|
|
|
}
|
|
|
|
|
|
/** the name of the current resolving dependency */
|
|
|
getName() {
|
|
|
return this._name;
|
|
|
}
|
|
|
|
|
|
createContainerLifetime<T>() {
|
|
|
return this._containerLifetimeManager.createLifetime<T>();
|
|
|
}
|
|
|
|
|
|
/** Resolves the specified dependency in the current context
|
|
|
* @param name The name of the dependency being resolved
|
|
|
*/
|
|
|
resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
|
|
|
/** Resolves the specified dependency with the specified default value if
|
|
|
* the dependency is missing.
|
|
|
*
|
|
|
* @param name The name of the dependency being resolved
|
|
|
* @param def A default value to return in case of the specified dependency
|
|
|
* is missing.
|
|
|
*/
|
|
|
resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
|
|
|
resolve<K extends keyof S, T>(name: K, def?: T): S[K] | 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<K extends ConfigurableKeys<S>>(name: K, service: RegistrationMap<S>[K]) {
|
|
|
argumentNotNull(name, "name");
|
|
|
|
|
|
this._services[name] = service;
|
|
|
}
|
|
|
|
|
|
createLifetime<T>(): ILifetime<T> {
|
|
|
const id = nextId++;
|
|
|
return {
|
|
|
initialize() {},
|
|
|
has: () => id in this._cache,
|
|
|
get: () => this._cache[id] as T,
|
|
|
store: item => {
|
|
|
this._cache[id] = item;
|
|
|
}
|
|
|
};
|
|
|
}
|
|
|
|
|
|
activate<T>(d: Descriptor<S, T>, name: string) {
|
|
|
// TODO: add logging
|
|
|
// if (trace.isLogEnabled())
|
|
|
// trace.log("enter {0} {1}", name, d);
|
|
|
|
|
|
const ctx = this.enter(d, name);
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
private enter(service: Descriptor<S, unknown>, name: string): this {
|
|
|
const clone = Object.create(this) as this;
|
|
|
clone._name = name;
|
|
|
clone._services = Object.create(this._services) as typeof this._services;
|
|
|
clone._parent = this;
|
|
|
clone._service = service;
|
|
|
return clone;
|
|
|
}
|
|
|
|
|
|
/** Creates a clone for the current context, used to protect it from modifications */
|
|
|
clone(): this {
|
|
|
const clone = Object.create(this) as this;
|
|
|
clone._services = Object.create(this._services) as typeof this._services;
|
|
|
return clone;
|
|
|
}
|
|
|
}
|
|
|
|