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 implements IDescriptorBuilder { private readonly _cb: (d: Descriptor) => void; private readonly _eb: (err: unknown) => void; private readonly _refs: DepsMap; private readonly _level: number; private readonly _instanceId: string | number; private _lifetime: ILifetime = emptyLifetime(); private _overrides: DescriptorMap; private _cleanup?: (item: T) => void; private _factory?: (refs: R) => NonNullable; 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) => 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 & Record>(refs: X): IDescriptorBuilder; }, U> { each(refs, (v, k) => this._refs[k] = v); return this as IDescriptorBuilder; }, U>; } /** Registers a factory method for the service */ factory(f: (refs: R) => NonNullable): 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(name: K, builder: BuildDescriptorFn, U>): this; override(services: { [k in K]: BuildDescriptorFn, U> }): this; override(nameOrServices: K | { [name in K]: BuildDescriptorFn, U> }, builder?: BuildDescriptorFn, U>): this { this._assertBuilding(); const guard = (v: void | Promise) => { if (isPromise(v)) v.catch(err => this._fail(err)); }; if (typeof nameOrServices !== "object") { if (builder) { this._defer(); const d = new DescriptorBuilder, 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} * object or {@linkcode ActivationType} literal. * @param lifetime */ lifetime(lifetime: ILifetime | Exclude): this; lifetime(lifetime: ILifetime | 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): void { this._assertBuilding(); this._cb({ activate() { return v; } }); this._finalize(); } _resolveLifetime(activation: ActivationType, typeId?: string | object): ILifetime { 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({ lifetime: this._lifetime, factory: this._factory as (refs: Record) => NonNullable, overrides: this._overrides, cleanup: this._cleanup })); } } _fail(err: unknown) { if (!this._failed) { this._failed = true; this._eb.call(undefined, err); } } }