|
|
import { BuildDescriptorFn, IDescriptorBuilder, DepsMap, Resolve, DescriptorMap } from "./interfaces";
|
|
|
import { Descriptor, ILifetime, ActivationType } from "./interfaces";
|
|
|
import { DescriptorImpl } from "./DescriptorImpl";
|
|
|
import { containerLifetime, contextLifetime, emptyLifetime, hierarchyLifetime, LifetimeManager, singletonLifetime } from "./LifetimeManager";
|
|
|
import { each, isPromise, isString, key, oid } from "./traits";
|
|
|
|
|
|
/**
|
|
|
* @template {S} Карта доступных зависимостей, как правило `ContainerServices`
|
|
|
* @template {T} Тип сервиса
|
|
|
*/
|
|
|
export class DescriptorBuilder<S, T, R, U extends keyof S> implements IDescriptorBuilder<S, T, R, U> {
|
|
|
private readonly _cb: (d: Descriptor<S, T>) => void;
|
|
|
|
|
|
private readonly _eb: (err: unknown) => void;
|
|
|
|
|
|
private readonly _refs: DepsMap<R>;
|
|
|
|
|
|
private _lifetime: ILifetime<T> = emptyLifetime<T>();
|
|
|
|
|
|
private _overrides: DescriptorMap<S>;
|
|
|
|
|
|
private _cleanup?: (item: T) => void;
|
|
|
|
|
|
private _factory?: (refs: R) => NonNullable<T>;
|
|
|
|
|
|
private _pending = 1;
|
|
|
|
|
|
private _failed = false;
|
|
|
|
|
|
private _finalized = false;
|
|
|
|
|
|
/**
|
|
|
* Creates new DescriptorBuilder. Accepts a lifetime container for resolving "container"
|
|
|
* lifetime.
|
|
|
*
|
|
|
* @param cb The callback to receive the built service descriptor
|
|
|
* @param eb The callback to receive the error due
|
|
|
*/
|
|
|
constructor(cb: (d: Descriptor<S, T>) => void, eb: (err: unknown) => void) {
|
|
|
this._cb = cb;
|
|
|
this._eb = eb;
|
|
|
this._overrides = {};
|
|
|
this._refs = {};
|
|
|
}
|
|
|
|
|
|
/** Declares dependencies to be consumed in the factory method */
|
|
|
wants<X extends DepsMap<S> & Record<keyof R & keyof X, never>>(refs: X):
|
|
|
IDescriptorBuilder<S, T, R & { [k in keyof X]: Resolve<S, X[k]>; }, U> {
|
|
|
|
|
|
each(refs, (v, k) => this._refs[k] = v);
|
|
|
|
|
|
return this as IDescriptorBuilder<S, T, R & {
|
|
|
[k in keyof X]: Resolve<S, X[k]>;
|
|
|
}, U>;
|
|
|
}
|
|
|
|
|
|
/** Registers a factory method for the service */
|
|
|
factory(f: (refs: R) => NonNullable<T>): void {
|
|
|
this._assertBuilding();
|
|
|
this._factory = f;
|
|
|
this._finalize();
|
|
|
this._complete();
|
|
|
}
|
|
|
|
|
|
|
|
|
private _assertBuilding() {
|
|
|
if (this._finalized)
|
|
|
throw new Error("The descriptor builder is finalized");
|
|
|
}
|
|
|
|
|
|
private _finalize() {
|
|
|
this._finalized = true;
|
|
|
}
|
|
|
|
|
|
override<K extends U>(name: K, builder: BuildDescriptorFn<S, NonNullable<S[K]>, U>): this;
|
|
|
override<K extends U>(services: { [k in K]: BuildDescriptorFn<S, NonNullable<S[k]>, U> }): this;
|
|
|
override<K extends U>(nameOrServices: K | { [name in K]: BuildDescriptorFn<S, NonNullable<S[K]>, U> }, builder?: BuildDescriptorFn<S, NonNullable<S[K]>, U>): this {
|
|
|
this._assertBuilding();
|
|
|
const guard = (v: void | Promise<void>) => {
|
|
|
if (isPromise(v))
|
|
|
v.catch(err => this._fail(err));
|
|
|
};
|
|
|
|
|
|
if (typeof nameOrServices !== "object") {
|
|
|
if (builder) {
|
|
|
this._defer();
|
|
|
const d = new DescriptorBuilder<S, NonNullable<S[K]>, object, U>(
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
/** Specified the singleton lifetime for the service */
|
|
|
lifetime(lifetime: "singleton", typeId: string): this;
|
|
|
/**
|
|
|
* Specifies the lifetime for the service, either {@linkcode ILifetime<T>}
|
|
|
* object or {@linkcode ActivationType} literal.
|
|
|
* @param lifetime
|
|
|
*/
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
/** Registers cleanup callback, used when lifetime of the instance is managed
|
|
|
* by the container or some external mechanism
|
|
|
*/
|
|
|
cleanup(cb: (item: T) => void): this {
|
|
|
this._assertBuilding();
|
|
|
this._cleanup = cb;
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
/** Registers a value as the instance of the service */
|
|
|
value(v: NonNullable<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 containerLifetime();
|
|
|
case "hierarchy":
|
|
|
return hierarchyLifetime();
|
|
|
case "context":
|
|
|
return contextLifetime();
|
|
|
case "singleton": {
|
|
|
if (!typeId)
|
|
|
throw Error("The singleton activation requires a typeId");
|
|
|
const _oid = isString(typeId) ? typeId : oid(typeId);
|
|
|
return singletonLifetime(_oid);
|
|
|
}
|
|
|
default:
|
|
|
return emptyLifetime();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
_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 as (refs: Record<key, unknown>) => NonNullable<T>,
|
|
|
overrides: this._overrides,
|
|
|
cleanup: this._cleanup
|
|
|
}));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
_fail(err: unknown) {
|
|
|
if (!this._failed) {
|
|
|
this._failed = true;
|
|
|
this._eb.call(undefined, err);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|