import { RegistrationBuilder, LifetimeContainer, ConfigurableKeys, IDescriptorBuilder, Ref, Resolved, DepsMap } from "./interfaces"; import { Descriptor, ILifetime, ActivationType } from "./interfaces"; import { DescriptorImpl, RegistrationOverridesMap } from "./DescriptorImpl"; import { LifetimeManager } from "./LifetimeManager"; import { each, isKey, isPromise, isString, key, oid } from "./traits"; /** * @template {S} Карта доступных зависимостей, как правило `ContainerServices` * @template {T} Тип сервиса */ export class DescriptorBuilder implements IDescriptorBuilder { private readonly _lifetimeManager: LifetimeManager; private readonly _cb: (d: Descriptor) => void; private readonly _eb: (err: unknown) => void; private readonly _refs: DepsMap; private _lifetime = LifetimeManager.empty(); private _overrides: RegistrationOverridesMap; private _cleanup?: (item: T) => void; private _factory?: (refs: R) => T; private _pending = 1; private _failed = false; private _finalized = false; /** * Creates new DescriptorBuilder. Accepts a lifetime container for resolving "container" * lifetime. * * @param lifetimeManager 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(lifetimeManager: LifetimeManager, cb: (d: Descriptor) => void, eb: (err: unknown) => void) { this._lifetimeManager = lifetimeManager; this._cb = cb; this._eb = eb; this._overrides = {}; this._refs = {}; } wants]: keyof S | Ref; }>(refs: X): IDescriptorBuilder : X[k] extends Ref ? Resolved : never; }, O> { each(refs, (v, k) => this._refs[k] = v); return this as IDescriptorBuilder : X[k] extends Ref ? Resolved : never; }, O>; } factory(f: (refs: R) => 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(name: K, builder: RegistrationBuilder>): this; override(services: { [k in K]: RegistrationBuilder> }): this; override(nameOrServices: K | { [name in K]: RegistrationBuilder> }, builder?: RegistrationBuilder>): this { this._assertBuilding(); const guard = (v: void | Promise) => { if (isPromise(v)) v.catch(err => this._fail(err)); }; if (isKey(nameOrServices)) { if (builder) { this._defer(); const d = new DescriptorBuilder, object, O>( this._lifetimeManager, 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 | 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; } cleanup(cb: (item: T) => void): this { this._assertBuilding(); this._cleanup = cb; return this; } value(v: T): void { this._assertBuilding(); this._cb({ activate() { return v; } }); this._finalize(); } _resolveLifetime(activation: ActivationType, typeId?: string | object): ILifetime { switch (activation) { case "container": return this._lifetimeManager.create(); 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({ 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); } } }