##// END OF EJS Templates
sync
sync

File last commit:

r15:3985e8405319 tip default
r15:3985e8405319 tip default
Show More
DescriptorBuilder.ts
197 lines | 6.4 KiB | video/mp2t | TypeScriptLexer
/ src / main / ts / DescriptorBuilder.ts
import { BuildDescriptorFn, IDescriptorBuilder, DepsMap, Resolve, DescriptorMap } from "../typings/interfaces";
import { Descriptor, ILifetime, ActivationType } from "../typings/interfaces";
import { DescriptorImpl } from "./DescriptorImpl";
import { contextLifetime, emptyLifetime, hierarchyLifetime, scopeLifetime, 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 readonly _level: number;
private readonly _instanceId: string | number;
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(level: number, instanceId: string | number, cb: (d: Descriptor<S, T>) => void, eb: (err: unknown) => void) {
this._cb = cb;
this._eb = eb;
this._overrides = {};
this._refs = {};
this._level = level;
this._instanceId = instanceId;
}
/** 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>(
this._level,
String(nameOrServices),
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 scopeLifetime(this._level, this._instanceId);
case "hierarchy":
return hierarchyLifetime(this._instanceId);
case "context":
return contextLifetime(this._instanceId);
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);
}
}
}