|
|
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<S extends object > implements ServiceContainer<S> {
|
|
|
private readonly _services: Partial<RegistrationMap<ContainerServices<S>>>;
|
|
|
|
|
|
private readonly _lifetimeManager: LifetimeManager;
|
|
|
|
|
|
private readonly _cleanup: (() => void)[];
|
|
|
|
|
|
private _disposed: boolean;
|
|
|
|
|
|
constructor(parent?: Container<S>) {
|
|
|
this._services = Object.create(parent ? parent._services : null) as typeof this._services;
|
|
|
this._cleanup = [];
|
|
|
this._services.container = { activate: () => this as ServiceLocator<ContainerServices<S>>};
|
|
|
this._services.childContainer = { activate: () => this.createChildContainer() };
|
|
|
this._disposed = false;
|
|
|
this._lifetimeManager = new LifetimeManager();
|
|
|
}
|
|
|
|
|
|
register<K extends ConfigurableKeys<S>>(name: K, service: Descriptor<ContainerServices<S>, ConfigurableServices<S>[K]>): void;
|
|
|
register<K extends ConfigurableKeys<S>>(services: {[k in K]: Descriptor<ContainerServices<S>, ConfigurableServices<S>[k]>}): void;
|
|
|
register<K extends ConfigurableKeys<S>>(nameOrCollection: K | {[k in K]: Descriptor<ContainerServices<S>, ConfigurableServices<S>[k]>}, service?: RegistrationMap<ContainerServices<S>>[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<S> {
|
|
|
return new Container(this);
|
|
|
}
|
|
|
|
|
|
resolve<K extends ContainerKeys<S>>(name: K): NonNullable<ContainerServices<S>[K]>;
|
|
|
resolve<K extends ContainerKeys<S>>(name: K, def: ContainerServices<S>[K]): ContainerServices<S>[K];
|
|
|
resolve<K extends ContainerKeys<S>>(name: K, def?: ContainerServices<S>[K]): ContainerServices<S>[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<T>() {
|
|
|
return this._lifetimeManager.create<T>();
|
|
|
}
|
|
|
|
|
|
destroy() {
|
|
|
return this.dispose();
|
|
|
}
|
|
|
dispose() {
|
|
|
if (this._disposed)
|
|
|
return;
|
|
|
this._disposed = true;
|
|
|
for (const f of this._cleanup)
|
|
|
f();
|
|
|
this._lifetimeManager.destroy();
|
|
|
}
|
|
|
}
|
|
|
|