|
|
import { ActivationItem } from "../ts/ActivationError";
|
|
|
import { key } from "../ts/traits";
|
|
|
|
|
|
export interface IDestroyable {
|
|
|
destroy(): void;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @template S Карта доступных зависимостей
|
|
|
*/
|
|
|
export interface Resolver<S> {
|
|
|
/**
|
|
|
* Функция для разрешения зависимостей, поддерживает создание фабричных методов,
|
|
|
* отложенную активацию и значение по-умолчанию для сервисов
|
|
|
* @template K Ключ сервиса из {@linkcode S}
|
|
|
* @template O Тип параметра {@linkcode opts} используется для выведения типа
|
|
|
* возвращаемого значения.
|
|
|
* @param name Ключ сервиса, который будет разрешен.
|
|
|
* @param {boolean=} opts.lazy Признак того, что требуется отложенная активация,
|
|
|
* будет возвращен фабричный метод для получения зависимости. Если не указан,
|
|
|
* то считается `false`.
|
|
|
* @param {any=} opts.default Значение по умолчанию, если в контейнере указанный
|
|
|
* сервис не зарегистрирован
|
|
|
* @returns Либо фабричный метод для получения зависимости, либо значение зависимости
|
|
|
* @throws Error Если зависимость не найдена и не предоставлено значение по-умолчанию
|
|
|
*/
|
|
|
<K extends keyof S, O extends { lazy: true; }>(name: K, opts?: O): () => NonNullable<S[K]> | InferDefault<O>;
|
|
|
<K extends keyof S, O extends { lazy?: false; }>(name: K, opts?: O): NonNullable<S[K]> | InferDefault<O>;
|
|
|
}
|
|
|
|
|
|
export type DepsMap<S> = {
|
|
|
[k in key]: Refs<S> | keyof S;
|
|
|
};
|
|
|
|
|
|
export type DescriptorDepsMap<S> = {
|
|
|
[k in key]: Refs<S>;
|
|
|
};
|
|
|
|
|
|
export type Refs<S> = {
|
|
|
[k in keyof S]: Ref<k, S[k]>;
|
|
|
}[keyof S];
|
|
|
|
|
|
export type Ref<K extends key, D> = {
|
|
|
/** The name of the service */
|
|
|
name: K;
|
|
|
|
|
|
/** Make a lazy reference, the resolved dependency will be a function */
|
|
|
lazy?: boolean;
|
|
|
|
|
|
/** The default value for the case where the service isn't defined.
|
|
|
* When specified the dependency becomes optional, the default value can be
|
|
|
* `null` or `undefined`
|
|
|
*/
|
|
|
default?: D;
|
|
|
};
|
|
|
|
|
|
export type Lazy<T, L extends boolean> = L extends true ? () => T : T;
|
|
|
|
|
|
/** Возвращает тип свойства `default` в типе {@link T} */
|
|
|
export type InferDefault<T> = T extends { default: infer D } ? D : never;
|
|
|
|
|
|
export type IsLazy<R> = R extends { lazy: infer L } ?
|
|
|
L extends true ? true : false :
|
|
|
false;
|
|
|
export type Resolve<S, R> =
|
|
|
R extends keyof S ? NonNullable<S[R]> :
|
|
|
R extends Ref<infer K, unknown> ?
|
|
|
K extends keyof S ?
|
|
|
Lazy<NonNullable<S[K]> | InferDefault<R>, IsLazy<R>> :
|
|
|
never :
|
|
|
never;
|
|
|
|
|
|
/**
|
|
|
* Интерфейс для конфигурирования сервиса в контейнере. Конфигурирование сервиса
|
|
|
* состоит из настройки различных параметров вызовами методов {@linkcode wants},
|
|
|
* {@linkcode lifetime}, {@linkcode override}, {@linkcode cleanup}. Завершение настройки
|
|
|
* сервиса осуществляется вызовом одного из методов {@linkcode factory} либо
|
|
|
* {@linkcode value}.
|
|
|
*
|
|
|
* @template S Карта сервисов контейнера, доступных при описании дескриптора
|
|
|
* @template T Тип сервиса
|
|
|
* @template R Карта зависимостей, которая передается параметром фабрике
|
|
|
* @template U Имена пользовательских сервисов, доступных для переопределения
|
|
|
*/
|
|
|
export interface IDescriptorBuilder<S, T, R, U extends keyof S> {
|
|
|
|
|
|
/** Указывает фабрика для создания экземпляра сервиса, фабрика передается
|
|
|
* в виде параметра. При вызове фабрике будет передан объект с зависимостями,
|
|
|
* которые были предварительно указаны вызовами метода `wants(...)`
|
|
|
*
|
|
|
* Вызов данного метода завершает конфигурирование сервиса.
|
|
|
*
|
|
|
* @param f Фабрика для создания экземпляра сервиса.
|
|
|
*/
|
|
|
factory(f: (refs: R) => NonNullable<T>): void;
|
|
|
|
|
|
/**
|
|
|
* Используется для указания зависимостей, которые потребуются фабричному
|
|
|
* методу при создании нового экземпляра сервиса. Данный метод может быть
|
|
|
* вызван несколько раз подряд, при этом вызовы этого метода имеют
|
|
|
* кумулятивный эффект.
|
|
|
*
|
|
|
* @template X Тип объекта с зависимостями, которые требуется получить при
|
|
|
* создании экземпляра фабрики при помощи фабричного метода.
|
|
|
* @param refs Объект с описанием зависимостей
|
|
|
* @returns Возвращает дескриптор сервиса, в котором указаны необходимые
|
|
|
* зависимости
|
|
|
*/
|
|
|
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>
|
|
|
|
|
|
override<K extends U>(name: K, builder: BuildDescriptorFn<S, S[K], U>): this;
|
|
|
override<X extends ConfigurationMapConstraint<S, U, keyof X>>(services: X): this;
|
|
|
|
|
|
lifetime(lifetime: "singleton", typeId: string | number | object): this;
|
|
|
lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this;
|
|
|
|
|
|
/** Указывает функцию для освобождения экземпляра сервиса для случаев, когда
|
|
|
* время жизни привязано к контейнеру.
|
|
|
*/
|
|
|
cleanup(cb: (item: T) => void): this;
|
|
|
|
|
|
/**
|
|
|
* Регистрирует в контейнере постоянное значение в качестве реализации сервиса.
|
|
|
*
|
|
|
* @param v Экземпляр реализации сервиса.
|
|
|
*/
|
|
|
value(v: NonNullable<T>): void;
|
|
|
}
|
|
|
|
|
|
export type BuildDescriptorFn<S, T, U extends keyof S> = (d: IDescriptorBuilder<S, T, Record<never, never>, U>) => void;
|
|
|
|
|
|
/**
|
|
|
* Конфигурация контейнера, состоит из набора функций, которые выполняют конфигурацию.
|
|
|
*
|
|
|
* Все параметры конфигурации являются обязательными, если требуется ввести
|
|
|
* необязательные параметры, то нужно ограничить параметр типа {@linkcode K}
|
|
|
*
|
|
|
* @template S Сервисы доступные в контейнере
|
|
|
* @template K Сервисы участвующие в конфигурации
|
|
|
*/
|
|
|
export type ConfigurationMap<S, K extends keyof S, U extends keyof S> = {
|
|
|
[k in K]-?: BuildDescriptorFn<S, S[k], U>
|
|
|
};
|
|
|
|
|
|
export type ConfigurationMapConstraint<S, U extends keyof S, X extends string | number | symbol> = {
|
|
|
[k in X]-?: k extends U ? BuildDescriptorFn<S, S[k], U> : never;
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* The type constraint useful to restrict type parameters to prevent defining
|
|
|
* the services with the {@link ContainerKeys} names.
|
|
|
*
|
|
|
* The constraint doesn't exclude using this keys but declares them as `never`
|
|
|
* which effectively will lead using this keys to the error.
|
|
|
*/
|
|
|
export type ContainerServicesConstraint<S> = {
|
|
|
[k in keyof S]: k extends ContainerKeys ? never : S[k];
|
|
|
};
|
|
|
|
|
|
export interface Descriptor<S, T> {
|
|
|
|
|
|
/** This flags indicates that this registration can be replaced or overridden. */
|
|
|
readonly configurable?: boolean;
|
|
|
|
|
|
activate(context: IActivationContext<S>, stack: ActivationItem[]): NonNullable<T>;
|
|
|
}
|
|
|
|
|
|
/** The context used to initialize lifetime instance {@linkcode ILifetime} */
|
|
|
export interface ILifetimeContext {
|
|
|
|
|
|
contextSlot<T>(slotId: string | number): ILifetimeSlot<T>;
|
|
|
|
|
|
/**
|
|
|
*
|
|
|
* @param slotId
|
|
|
*/
|
|
|
scopeSlot<T>(level: number, slotId: string | number): ILifetimeSlot<T>;
|
|
|
|
|
|
hierarchySlot<T>(slotId: string | number): ILifetimeSlot<T>;
|
|
|
}
|
|
|
|
|
|
export interface IActivationContext<S> extends ILifetimeContext {
|
|
|
|
|
|
/**
|
|
|
* @param overrides
|
|
|
* @param action
|
|
|
* @throws
|
|
|
*/
|
|
|
withOverrides<X>(overrides: DescriptorMap<S>, action: () => X): X;
|
|
|
|
|
|
selfContainer(): ServiceLocator<S>;
|
|
|
|
|
|
createChildContainer(): IContainerBuilder<S, keyof S>;
|
|
|
|
|
|
resolve<K extends keyof S>(name: K, stack: ActivationItem[]): NonNullable<S[K]>;
|
|
|
resolve<K extends keyof S, T>(name: K, stack: ActivationItem[], def: T): NonNullable<S[K]> | T;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Descriptors map for the specified services {@linkcode S}. All entries are
|
|
|
* optional regardless the required or optional services in the original map.
|
|
|
*
|
|
|
* @template S Сервисы контекста активации
|
|
|
* @template U Карта сервисов которые создаются дескрипторами
|
|
|
*/
|
|
|
export type DescriptorMap<S> = {
|
|
|
[k in keyof S]?: Descriptor<S, S[k]>;
|
|
|
};
|
|
|
|
|
|
export type ContainerKeys = keyof ContainerProvided<object>;
|
|
|
|
|
|
export type ContainerProvided<S, U extends keyof S = keyof S> = {
|
|
|
container: ServiceLocator<ContainerProvided<S>>;
|
|
|
|
|
|
childContainer: IContainerBuilder<S, U>;
|
|
|
};
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Таблица сервисов, которые предоставляет контейнер.
|
|
|
*
|
|
|
* Сервисы, предоставляемые контейнером не могут быть null или undefined.
|
|
|
*/
|
|
|
export type ContainerServices<S> = {
|
|
|
[k in (keyof S) | ContainerKeys]:
|
|
|
k extends ContainerKeys ? ContainerProvided<S>[k] :
|
|
|
k extends keyof S ? S[k] : never
|
|
|
};
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Returns the service declared in the type map {@link S}.
|
|
|
*
|
|
|
*
|
|
|
*/
|
|
|
export interface ServiceLocator<S> {
|
|
|
resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
|
|
|
resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
|
|
|
}
|
|
|
|
|
|
|
|
|
export interface IContainerBuilder<S, U extends keyof S> {
|
|
|
createServiceBuilder<K extends U>(name: K):
|
|
|
IDescriptorBuilder<S, S[K], Record<never, never>, U>;
|
|
|
|
|
|
build(): ServiceLocator<S>;
|
|
|
}
|
|
|
|
|
|
|
|
|
export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
|
|
|
|
|
|
/**
|
|
|
* Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет
|
|
|
* свой собственный объект `ILifetime`, который создается при первой активации
|
|
|
*/
|
|
|
export type ILifetime<T> = (context: ILifetimeContext) => ILifetimeSlot<T>;
|
|
|
|
|
|
export interface ILifetimeSlot<T> {
|
|
|
has(): boolean;
|
|
|
|
|
|
get(): NonNullable<T>;
|
|
|
|
|
|
/**
|
|
|
* Initializes the slot, if the slot is already initialized returns false,
|
|
|
* otherwise returns true. This method is used to detect cyclic references.
|
|
|
*/
|
|
|
initialize(): boolean;
|
|
|
|
|
|
store(item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void): void;
|
|
|
|
|
|
/**
|
|
|
* Removes the slot from the lifetime manager. The inner value will not be
|
|
|
* disposed. If the slot is cleaned up calling this method has no effect.
|
|
|
*/
|
|
|
remove(): void;
|
|
|
|
|
|
/**
|
|
|
* Disposed inner value of this slot.
|
|
|
*/
|
|
|
cleanup(): void;
|
|
|
}
|
|
|
|
|
|
export interface ILifetimeManager extends IDestroyable {
|
|
|
slot<T>(id: string | number): ILifetimeSlot<T>;
|
|
|
}
|
|
|
|
|
|
export type ExtractRequired<T, K extends keyof T = keyof T> = { [p in K as (undefined extends T[p] ? never : p)]-?: T[p] };
|
|
|
|
|
|
export type ExtractRequiredKeys<T, K extends keyof T = keyof T> = { [p in K]-?: undefined extends T[p] ? never : p }[K];
|