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