|
|
import { Resolver, RegistrationBuilder, LifetimeContainer, ConfigurableKeys } from "./interfaces";
|
|
|
import { Descriptor, ILifetime, ActivationType } from "./interfaces";
|
|
|
import { DescriptorImpl, RegistrationOverridesMap } from "./DescriptorImpl";
|
|
|
import { LifetimeManager } from "./LifetimeManager";
|
|
|
import { each, isKey, isPromise, isString, oid } from "./traits";
|
|
|
|
|
|
/**
|
|
|
* @template {S} Карта доступных зависимостей, как правило `ContainerServices`
|
|
|
* @template {T} Тип сервиса
|
|
|
*/
|
|
|
export class DescriptorBuilder<S extends object, T> {
|
|
|
private readonly _lifetimeContainer: LifetimeContainer;
|
|
|
private readonly _cb: (d: Descriptor<S, T>) => void;
|
|
|
|
|
|
private readonly _eb: (err: unknown) => void;
|
|
|
|
|
|
private _lifetime = LifetimeManager.empty<T>();
|
|
|
|
|
|
private _overrides: RegistrationOverridesMap<S>;
|
|
|
|
|
|
private _cleanup?: (item: T) => void;
|
|
|
|
|
|
private _factory?: (resolve: Resolver<S>) => T;
|
|
|
|
|
|
private _pending = 1;
|
|
|
|
|
|
private _failed = false;
|
|
|
|
|
|
private _finalized = false;
|
|
|
|
|
|
/**
|
|
|
* Creates new DescriptorBuilder. Accepts a lifetime container for resolving "container"
|
|
|
* lifetime.
|
|
|
*
|
|
|
* @param container The lifetime container is the container where the service is to be registered.
|
|
|
* @param cb The callback to receive the built service descriptor
|
|
|
* @param eb The callback to receive the error due
|
|
|
*/
|
|
|
constructor(container: LifetimeContainer, cb: (d: Descriptor<S, T>) => void, eb: (err: unknown) => void) {
|
|
|
this._lifetimeContainer = container;
|
|
|
this._cb = cb;
|
|
|
this._eb = eb;
|
|
|
this._overrides = {};
|
|
|
}
|
|
|
|
|
|
private _assertBuilding() {
|
|
|
if (this._finalized)
|
|
|
throw new Error("The descriptor builder is finalized");
|
|
|
}
|
|
|
|
|
|
private _finalize() {
|
|
|
this._finalized = true;
|
|
|
}
|
|
|
|
|
|
override<K extends ConfigurableKeys<S>>(name: K, builder: RegistrationBuilder<S, NonNullable<S[K]>>): this;
|
|
|
override<K extends ConfigurableKeys<S>>(services: { [k in K]: RegistrationBuilder<S, NonNullable<S[k]>> }): this;
|
|
|
override<K extends ConfigurableKeys<S>>(nameOrServices: K | { [name in K]: RegistrationBuilder<S, NonNullable<S[K]>> }, builder?: RegistrationBuilder<S, NonNullable<S[K]>>): this {
|
|
|
this._assertBuilding();
|
|
|
const guard = (v: void | Promise<void>) => {
|
|
|
if (isPromise(v))
|
|
|
v.catch(err => this._fail(err));
|
|
|
};
|
|
|
|
|
|
if (isKey(nameOrServices)) {
|
|
|
if (builder) {
|
|
|
this._defer();
|
|
|
const d = new DescriptorBuilder<S, NonNullable<S[K]>>(
|
|
|
this._lifetimeContainer,
|
|
|
result => {
|
|
|
this._overrides[nameOrServices] = result;
|
|
|
this._complete();
|
|
|
},
|
|
|
err => this._fail(err)
|
|
|
);
|
|
|
|
|
|
try {
|
|
|
guard(builder(d));
|
|
|
} catch (err) {
|
|
|
this._fail(err);
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
each(nameOrServices, (v, k) => this.override(k, v));
|
|
|
}
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
lifetime(lifetime: "singleton", typeId: string): this;
|
|
|
lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this;
|
|
|
lifetime(lifetime: ILifetime<T> | ActivationType, typeId?: string): this {
|
|
|
this._assertBuilding();
|
|
|
if (isString(lifetime)) {
|
|
|
this._lifetime = this._resolveLifetime(lifetime, typeId);
|
|
|
} else {
|
|
|
this._lifetime = lifetime;
|
|
|
}
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
cleanup(cb: (item: T) => void): this {
|
|
|
this._assertBuilding();
|
|
|
this._cleanup = cb;
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
factory(f: (resolve: Resolver<S>) => T): void {
|
|
|
this._assertBuilding();
|
|
|
this._factory = f;
|
|
|
this._finalize();
|
|
|
this._complete();
|
|
|
}
|
|
|
|
|
|
value(v: T): void {
|
|
|
this._assertBuilding();
|
|
|
this._cb({
|
|
|
activate() {
|
|
|
return v;
|
|
|
}
|
|
|
});
|
|
|
this._finalize();
|
|
|
}
|
|
|
|
|
|
_resolveLifetime<T>(activation: ActivationType, typeId?: string | object): ILifetime<T> {
|
|
|
switch (activation) {
|
|
|
case "container":
|
|
|
return LifetimeManager.containerLifetime(this._lifetimeContainer);
|
|
|
case "hierarchy":
|
|
|
return LifetimeManager.hierarchyLifetime();
|
|
|
case "context":
|
|
|
return LifetimeManager.contextLifetime();
|
|
|
case "singleton": {
|
|
|
if (!typeId)
|
|
|
throw Error("The singleton activation requires a typeId");
|
|
|
|
|
|
const _oid = isString(typeId) ? typeId : oid(typeId);
|
|
|
|
|
|
return LifetimeManager.singletonLifetime(_oid);
|
|
|
}
|
|
|
default:
|
|
|
return LifetimeManager.empty();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
_defer() {
|
|
|
this._pending++;
|
|
|
}
|
|
|
|
|
|
_complete() {
|
|
|
if (--this._pending === 0) {
|
|
|
if (!this._factory)
|
|
|
throw new Error("The factory must be specified");
|
|
|
|
|
|
this._cb(new DescriptorImpl<S, T>({
|
|
|
lifetime: this._lifetime,
|
|
|
factory: this._factory,
|
|
|
overrides: this._overrides,
|
|
|
cleanup: this._cleanup
|
|
|
}));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
_fail(err: unknown) {
|
|
|
if (!this._failed) {
|
|
|
this._failed = true;
|
|
|
this._eb.call(undefined, err);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|