| @@ -1,71 +1,71 | |||||
| 1 | import { DescriptorBuilder } from "./DescriptorBuilder"; |
|
1 | import { DescriptorBuilder } from "./DescriptorBuilder"; | |
| 2 | import { ConfigurableKeys, ContainerServices, RegistrationBuildersMap, ExtractRequired, IContainerBuilder } from "./interfaces"; |
|
2 | import { ConfigurableKeys, ContainerServices, RegistrationBuildersMap, ExtractRequired, IContainerBuilder } from "./interfaces"; | |
| 3 | import { ServiceContainer } from "./interfaces"; |
|
3 | import { ServiceContainer } from "./interfaces"; | |
| 4 | import { argumentNotNull, each, isKey } from "./traits"; |
|
4 | import { argumentNotNull, each, isKey } from "./traits"; | |
| 5 |
|
5 | |||
| 6 |
export class FluentConfiguration<S |
|
6 | export class FluentConfiguration<S, Y extends ConfigurableKeys<S> = ConfigurableKeys<S>> { | |
| 7 |
|
7 | |||
| 8 | private _builders: Partial<RegistrationBuildersMap<S>> = {}; |
|
8 | private _builders: Partial<RegistrationBuildersMap<S>> = {}; | |
| 9 |
|
9 | |||
| 10 | /** Adds a declaration of the services to the current config. |
|
10 | /** Adds a declaration of the services to the current config. | |
| 11 | * |
|
11 | * | |
| 12 | * @template D The map of the services |
|
12 | * @template D The map of the services | |
| 13 | * @returns self |
|
13 | * @returns self | |
| 14 | */ |
|
14 | */ | |
| 15 |
declare<D extends |
|
15 | declare<D extends Pick<S, keyof D & keyof S>>(): FluentConfiguration<S & D, Y | ConfigurableKeys<D>> { | |
| 16 | return this as FluentConfiguration<S & D, Y | ConfigurableKeys<D>>; |
|
16 | return this as FluentConfiguration<S & D, Y | ConfigurableKeys<D>>; | |
| 17 | } |
|
17 | } | |
| 18 |
|
18 | |||
| 19 | /** Adds compile-time information about the already provided services |
|
19 | /** Adds compile-time information about the already provided services | |
| 20 | * |
|
20 | * | |
| 21 | * @template P The map of the provided services |
|
21 | * @template P The map of the provided services | |
| 22 | * @returns self |
|
22 | * @returns self | |
| 23 | */ |
|
23 | */ | |
| 24 | provided<P extends Pick<S, keyof P & keyof S>>(): FluentConfiguration<S & P, Exclude<Y, keyof P>> { |
|
24 | provided<P extends Pick<S, keyof P & keyof S>>(): FluentConfiguration<S & P, Exclude<Y, keyof P>> { | |
| 25 | return this as FluentConfiguration<S & P, Exclude<Y, keyof P>>; |
|
25 | return this as FluentConfiguration<S & P, Exclude<Y, keyof P>>; | |
| 26 | } |
|
26 | } | |
| 27 |
|
27 | |||
| 28 | /** Register the service. |
|
28 | /** Register the service. | |
| 29 | * |
|
29 | * | |
| 30 | * @param name The name of the service |
|
30 | * @param name The name of the service | |
| 31 | * @param builder The service builder |
|
31 | * @param builder The service builder | |
| 32 | * @returns self |
|
32 | * @returns self | |
| 33 | */ |
|
33 | */ | |
| 34 | register<K extends Y>(name: K, builder: RegistrationBuildersMap<S>[K]): FluentConfiguration<S, Exclude<Y, K>>; |
|
34 | register<K extends Y>(name: K, builder: RegistrationBuildersMap<S>[K]): FluentConfiguration<S, Exclude<Y, K>>; | |
| 35 | /** Registers the collection of services |
|
35 | /** Registers the collection of services | |
| 36 | * @param config The collection of services to register. |
|
36 | * @param config The collection of services to register. | |
| 37 | * @returns self |
|
37 | * @returns self | |
| 38 | */ |
|
38 | */ | |
| 39 | register<K extends Y>(config: RegistrationBuildersMap<S, K>): FluentConfiguration<S, Exclude<Y, K>>; |
|
39 | register<K extends Y>(config: RegistrationBuildersMap<S, K>): FluentConfiguration<S, Exclude<Y, K>>; | |
| 40 | register<K extends Y>(nameOrConfig: K | RegistrationBuildersMap<S, K>, builder?: RegistrationBuildersMap<S>[K]) { |
|
40 | register<K extends Y>(nameOrConfig: K | RegistrationBuildersMap<S, K>, builder?: RegistrationBuildersMap<S>[K]) { | |
| 41 | if (isKey(nameOrConfig)) { |
|
41 | if (isKey(nameOrConfig)) { | |
| 42 | argumentNotNull(builder, "builder"); |
|
42 | argumentNotNull(builder, "builder"); | |
| 43 | this._builders[nameOrConfig] = builder; |
|
43 | this._builders[nameOrConfig] = builder; | |
| 44 | } else { |
|
44 | } else { | |
| 45 | each(nameOrConfig, (v, k) => this.register(k, v)); |
|
45 | each(nameOrConfig, (v, k) => this.register(k, v)); | |
| 46 | } |
|
46 | } | |
| 47 |
|
47 | |||
| 48 | return this as FluentConfiguration<S, Exclude<Y, K>>; |
|
48 | return this as FluentConfiguration<S, Exclude<Y, K>>; | |
| 49 | } |
|
49 | } | |
| 50 |
|
50 | |||
| 51 | /** |
|
51 | /** | |
| 52 | * This method is used to enable a compile time check of the configuration. |
|
52 | * This method is used to enable a compile time check of the configuration. | |
| 53 | * If there are not configured services in the configuration the compiler |
|
53 | * If there are not configured services in the configuration the compiler | |
| 54 | * will trigger the error. |
|
54 | * will trigger the error. | |
| 55 | * |
|
55 | * | |
| 56 | * @param missing Empty object literal should always be specified. |
|
56 | * @param missing Empty object literal should always be specified. | |
| 57 | * @returns self |
|
57 | * @returns self | |
| 58 | */ |
|
58 | */ | |
| 59 | // eslint-disable-next-line @typescript-eslint/no-unused-vars |
|
59 | // eslint-disable-next-line @typescript-eslint/no-unused-vars | |
| 60 | done<M extends ExtractRequired<S,Y>>(missing: M) { |
|
60 | done<M extends ExtractRequired<S,Y>>(missing: M) { | |
| 61 | return this; |
|
61 | return this; | |
| 62 | } |
|
62 | } | |
| 63 |
|
63 | |||
| 64 | configure<T extends IContainerBuilder<S>>(builder: T) { |
|
64 | configure<T extends IContainerBuilder<S>>(builder: T) { | |
| 65 | each(this._builders, (v, k) => { |
|
65 | each(this._builders, (v, k) => { | |
| 66 | v(builder.createServiceBuilder(k)); |
|
66 | v(builder.createServiceBuilder(k)); | |
| 67 | }); |
|
67 | }); | |
| 68 | builder.build() as T & ServiceContainer<S>; |
|
68 | builder.build() as T & ServiceContainer<S>; | |
| 69 | } |
|
69 | } | |
| 70 |
|
70 | |||
| 71 | } |
|
71 | } | |
| @@ -1,154 +1,183 | |||||
| 1 | import { key } from "./traits"; |
|
1 | import { key } from "./traits"; | |
| 2 |
|
2 | |||
| 3 | export interface IDestroyable { |
|
3 | export interface IDestroyable { | |
| 4 | destroy(): void; |
|
4 | destroy(): void; | |
| 5 | } |
|
5 | } | |
| 6 |
|
6 | |||
| 7 | /** |
|
7 | /** | |
| 8 | * @template S Карта доступных зависимостей |
|
8 | * @template S Карта доступных зависимостей | |
| 9 | */ |
|
9 | */ | |
| 10 |
export interface Resolver<S |
|
10 | export interface Resolver<S> { | |
| 11 | /** |
|
11 | /** | |
| 12 | * Функция для разрешения зависимостей, поддерживает создание фабричных методов, |
|
12 | * Функция для разрешения зависимостей, поддерживает создание фабричных методов, | |
| 13 | * отложенную активацию и значение по-умолчанию для сервисов |
|
13 | * отложенную активацию и значение по-умолчанию для сервисов | |
| 14 | * @template K Ключ сервиса из {@link S} |
|
14 | * @template K Ключ сервиса из {@link S} | |
| 15 | * @template O Тип параметра {@link opts} используется для выведения типа |
|
15 | * @template O Тип параметра {@link opts} используется для выведения типа | |
| 16 | * возвращаемого значения. |
|
16 | * возвращаемого значения. | |
| 17 | * @param name Ключ сервиса, который будет разрешен. |
|
17 | * @param name Ключ сервиса, который будет разрешен. | |
| 18 | * @param {boolean=} opts.lazy Признак того, что требуется отложенная активация, |
|
18 | * @param {boolean=} opts.lazy Признак того, что требуется отложенная активация, | |
| 19 | * будет возвращен фабричный метод для получения зависимости. Если не указан, |
|
19 | * будет возвращен фабричный метод для получения зависимости. Если не указан, | |
| 20 | * то считается `false`. |
|
20 | * то считается `false`. | |
| 21 | * @param {any=} opts.default Значение по умолчанию, если в контейнере указанный |
|
21 | * @param {any=} opts.default Значение по умолчанию, если в контейнере указанный | |
| 22 | * сервис не зарегистрирован |
|
22 | * сервис не зарегистрирован | |
| 23 | * @returns Либо фабричный метод для получения зависимости, либо значение зависимости |
|
23 | * @returns Либо фабричный метод для получения зависимости, либо значение зависимости | |
| 24 | * @throws Error Если зависимость не найдена и не предоставлено значение по-умолчанию |
|
24 | * @throws Error Если зависимость не найдена и не предоставлено значение по-умолчанию | |
| 25 | */ |
|
25 | */ | |
| 26 | <K extends keyof S, O extends { lazy: true; default?: unknown }>(name: K, opts?: O): () => (O extends { default: infer T } ? T : never) | NonNullable<S[K]>; |
|
26 | <K extends keyof S, O extends { lazy: true; default?: unknown }>(name: K, opts?: O): () => (O extends { default: infer T } ? T : never) | NonNullable<S[K]>; | |
| 27 | <K extends keyof S, O extends { lazy?: false; default?: unknown }>(name: K, opts?: O): (O extends { default: infer T } ? T : never) | NonNullable<S[K]>; |
|
27 | <K extends keyof S, O extends { lazy?: false; default?: unknown }>(name: K, opts?: O): (O extends { default: infer T } ? T : never) | NonNullable<S[K]>; | |
| 28 | } |
|
28 | } | |
| 29 |
|
29 | |||
| 30 |
export type DepsMap<S |
|
30 | export type DepsMap<S> = { | |
| 31 | [k in key]: Refs<S> |
|
31 | [k in key]: Refs<S> | keyof S; | |
| 32 | }; |
|
32 | }; | |
| 33 |
|
33 | |||
| 34 |
export type Refs<S |
|
34 | export type Refs<S> = { | |
| 35 | [k in keyof S]: Ref<k, boolean, S[k]>; |
|
35 | [k in keyof S]: Ref<k, boolean, S[k]>; | |
| 36 |
}[keyof S] |
|
36 | }[keyof S]; | |
| 37 |
|
37 | |||
| 38 | export type Ref<K extends key, L extends boolean, D> = { name: K, lazy?: L, default?: D | null }; |
|
38 | export type Ref<K extends key, L extends boolean, D> = { name: K, lazy?: L, default?: D | null }; | |
| 39 |
|
39 | |||
| 40 | export type Lazy<T, L extends boolean> = L extends true ? () => T : T; |
|
40 | export type Lazy<T, L extends boolean> = L extends true ? () => T : T; | |
| 41 |
|
41 | |||
| 42 | export type InferDefault<T> = T extends { default: infer D } ? D : never; |
|
42 | export type InferDefault<T> = T extends { default: infer D } ? D : never; | |
| 43 |
|
43 | |||
| 44 |
export type Resolve<S |
|
44 | export type Resolve<S, R> = | |
| 45 | R extends keyof S ? NonNullable<S[R]> : |
|
45 | R extends keyof S ? NonNullable<S[R]> : | |
| 46 |
R extends Ref<infer K, infer L, unknown> ? |
|
46 | R extends Ref<infer K, infer L, unknown> ? | |
| 47 |
|
|
47 | K extends keyof S ? Lazy<NonNullable<S[K]> | InferDefault<R>, L> : | |
| 48 |
|
|
48 | never : | |
| 49 | never; |
|
49 | never; | |
| 50 |
|
50 | |||
| 51 | export interface IDescriptorBuilder<S extends object, T, R extends object, O extends keyof S> { |
|
51 | /** | |
|
|
52 | * Интерфейс для конфигурирования сервиса в контейнере | |||
|
|
53 | */ | |||
|
|
54 | export interface IDescriptorBuilder<S, T, R, O extends keyof S> { | |||
| 52 |
|
55 | |||
| 53 | /** |
|
56 | /** Указывает фабрика для создания экземпляра сервиса, фабрика передается | |
|
|
57 | * в виде параметра. При вызове фабрике будет передан объект с зависимостями, | |||
|
|
58 | * которые были предварительно указаны вызовами метода `wants(...)` | |||
| 54 | * |
|
59 | * | |
| 55 | * @param f |
|
60 | * Вызов данного метода завершает конфигурирование сервиса. | |
|
|
61 | * | |||
|
|
62 | * @param f Фабрика для создания экземпляра сервиса | |||
| 56 | */ |
|
63 | */ | |
| 57 | factory(f: (refs: R) => T): void; |
|
64 | factory(f: (refs: R) => T): void; | |
| 58 |
|
65 | |||
|
|
66 | /** | |||
|
|
67 | * Используется для указания зависимостей, которые потребуются фабричному | |||
|
|
68 | * методу при создании нового экземпляра сервиса. Данный метод может быть | |||
|
|
69 | * вызван несколько раз подряд, при этом вызовы этого метода имеют | |||
|
|
70 | * кумулятивный эффект. | |||
|
|
71 | * | |||
|
|
72 | * @template X Тип объекта с зависимостями, которые требуется получить при | |||
|
|
73 | * создании экземпляра фабрики при помощи фабричного метода. | |||
|
|
74 | * @param refs Объект с описанием зависимостей | |||
|
|
75 | * @returns Возвращает дескриптор сервиса, в котором указаны необходимые | |||
|
|
76 | * зависимости | |||
|
|
77 | */ | |||
| 59 | wants<X extends DepsMap<S> & Record<keyof R & keyof X, never>>(refs: X): |
|
78 | wants<X extends DepsMap<S> & Record<keyof R & keyof X, never>>(refs: X): | |
| 60 | IDescriptorBuilder<S, T, R & { |
|
79 | IDescriptorBuilder<S, T, R & { | |
| 61 | [k in keyof X]: Resolve<S, X[k]>; |
|
80 | [k in keyof X]: Resolve<S, X[k]>; | |
| 62 | }, O> |
|
81 | }, O> | |
| 63 |
|
82 | |||
| 64 | override<K extends O>(name: K, builder: RegistrationBuilder<S, NonNullable<S[K]>>): this; |
|
83 | override<K extends O>(name: K, builder: RegistrationBuilder<S, NonNullable<S[K]>>): this; | |
| 65 | override<K extends O>(services: { [name in K]: RegistrationBuilder<S, NonNullable<S[K]>> }): this; |
|
84 | override<K extends O>(services: { [name in K]: RegistrationBuilder<S, NonNullable<S[K]>> }): this; | |
| 66 |
|
85 | |||
| 67 | lifetime(lifetime: "singleton", typeId: string | number | object): this; |
|
86 | lifetime(lifetime: "singleton", typeId: string | number | object): this; | |
| 68 | lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this; |
|
87 | lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this; | |
| 69 |
|
88 | |||
|
|
89 | /** Указывает функцию для освобождения экземпляра сервиса для случаев, когда | |||
|
|
90 | * время жизни привязано к контейнеру. | |||
|
|
91 | */ | |||
| 70 | cleanup(cb: (item: T) => void): this; |
|
92 | cleanup(cb: (item: T) => void): this; | |
| 71 |
|
93 | |||
|
|
94 | /** | |||
|
|
95 | * Регистрирует в контейнере постоянное значение в качестве реализации сервиса. | |||
|
|
96 | * | |||
|
|
97 | * @param v Экземпляр реализации сервиса. | |||
|
|
98 | */ | |||
| 72 | value(v: T): void; |
|
99 | value(v: T): void; | |
| 73 | } |
|
100 | } | |
| 74 |
|
101 | |||
| 75 |
export type RegistrationBuilder<S |
|
102 | export type RegistrationBuilder<S, T> = (d: IDescriptorBuilder<S, T, object, ConfigurableKeys<S>>) => void; | |
| 76 |
|
103 | |||
| 77 | export type RegistrationBuildersMap<S extends Configurable<S>, K extends keyof S = keyof S> = { |
|
104 | export type RegistrationBuildersMap<S extends Configurable<S>, K extends keyof S = keyof S> = { | |
| 78 | [k in K]-?: RegistrationBuilder<ContainerServices<S>, NonNullable<S[k]>> |
|
105 | [k in K]-?: RegistrationBuilder<ContainerServices<S>, NonNullable<S[k]>> | |
| 79 | }; |
|
106 | }; | |
| 80 |
|
107 | |||
| 81 |
export interface Descriptor<S |
|
108 | export interface Descriptor<S, T> { | |
| 82 | activate(context: IActivationContext<S>): T; |
|
109 | activate(context: IActivationContext<S>): T; | |
| 83 | } |
|
110 | } | |
| 84 |
|
111 | |||
| 85 |
export interface IActivationContext<S |
|
112 | export interface IActivationContext<S> extends ServiceLocator<S> { | |
| 86 | createLifetime<T>(): ILifetime<T>; |
|
113 | createLifetime<T>(): ILifetime<T>; | |
| 87 |
|
114 | |||
| 88 | createContainerLifetime<T>(): ILifetime<T>; |
|
115 | createContainerLifetime<T>(): ILifetime<T>; | |
| 89 | } |
|
116 | } | |
| 90 |
|
117 | |||
| 91 |
export type RegistrationMap<S |
|
118 | export type RegistrationMap<S, K extends keyof S = keyof S> = { | |
| 92 | [k in K]-?: Descriptor<S, S[k]>; |
|
119 | [k in K]-?: Descriptor<S, S[k]>; | |
| 93 | }; |
|
120 | }; | |
| 94 |
|
121 | |||
| 95 |
export interface ContainerProvided<S |
|
122 | export interface ContainerProvided<S> { | |
| 96 | container: ServiceLocator<ContainerServices<S>>; |
|
123 | container: ServiceLocator<ContainerServices<S>>; | |
| 97 |
|
124 | |||
| 98 | childContainer: IContainerBuilder<S>; |
|
125 | childContainer: IContainerBuilder<S>; | |
| 99 | } |
|
126 | } | |
| 100 |
|
127 | |||
| 101 |
export type Configurable<S> = { [k in keyof S |
|
128 | export type Configurable<S> = { [k in keyof S]: k extends ProvidedKeys ? never : S[k]; }; | |
| 102 |
|
129 | |||
| 103 | export type ProvidedKeys = keyof ContainerProvided<never>; |
|
130 | export type ProvidedKeys = keyof ContainerProvided<never>; | |
| 104 |
|
131 | |||
| 105 | export type ContainerServices<S extends Configurable<S>> = S & ContainerProvided<S>; |
|
132 | export type ContainerServices<S> = | |
|
|
133 | { [k in keyof S as k extends ProvidedKeys ? never: k]: S[k] } & | |||
|
|
134 | ContainerProvided<S>; | |||
| 106 |
|
135 | |||
| 107 |
export type ConfigurableKeys<S |
|
136 | export type ConfigurableKeys<S> = Exclude<keyof S, ProvidedKeys>; | |
| 108 |
|
137 | |||
| 109 |
export type ConfigurableServices<S |
|
138 | export type ConfigurableServices<S> = Pick<S, ConfigurableKeys<S>>; | |
| 110 |
|
139 | |||
| 111 |
export type ContainerKeys<S |
|
140 | export type ContainerKeys<S> = keyof S | ProvidedKeys; | |
| 112 |
|
141 | |||
| 113 |
export interface ServiceLocator<S |
|
142 | export interface ServiceLocator<S> { | |
| 114 | resolve<K extends keyof S>(name: K): NonNullable<S[K]>; |
|
143 | resolve<K extends keyof S>(name: K): NonNullable<S[K]>; | |
| 115 | resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T; |
|
144 | resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T; | |
| 116 | } |
|
145 | } | |
| 117 |
|
146 | |||
| 118 | export interface LifetimeContainer { |
|
147 | export interface LifetimeContainer { | |
| 119 | createLifetime<T>(): ILifetime<T>; |
|
148 | createLifetime<T>(): ILifetime<T>; | |
| 120 | } |
|
149 | } | |
| 121 |
|
150 | |||
| 122 |
export interface ServiceContainer<S |
|
151 | export interface ServiceContainer<S> extends | |
| 123 | ServiceLocator<ContainerServices<S>>, |
|
152 | ServiceLocator<ContainerServices<S>>, | |
| 124 | IDestroyable { |
|
153 | IDestroyable { | |
| 125 |
|
154 | |||
| 126 | createChildContainer(): IContainerBuilder<S>; |
|
155 | createChildContainer(): IContainerBuilder<S>; | |
| 127 | } |
|
156 | } | |
| 128 |
|
157 | |||
| 129 |
export interface IContainerBuilder<S |
|
158 | export interface IContainerBuilder<S> { | |
| 130 | createServiceBuilder<K extends keyof S>(name: K): IDescriptorBuilder<S, NonNullable<S[K]>, object, keyof S>; |
|
159 | createServiceBuilder<K extends keyof S>(name: K): IDescriptorBuilder<S, NonNullable<S[K]>, object, keyof S>; | |
| 131 |
|
160 | |||
| 132 | build(): ServiceContainer<S>; |
|
161 | build(): ServiceContainer<S>; | |
| 133 | } |
|
162 | } | |
| 134 |
|
163 | |||
| 135 |
|
164 | |||
| 136 | export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call"; |
|
165 | export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call"; | |
| 137 |
|
166 | |||
| 138 | /** |
|
167 | /** | |
| 139 | * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет |
|
168 | * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет | |
| 140 | * свой собственный объект `ILifetime`, который создается при первой активации |
|
169 | * свой собственный объект `ILifetime`, который создается при первой активации | |
| 141 | */ |
|
170 | */ | |
| 142 | export interface ILifetime<T> { |
|
171 | export interface ILifetime<T> { | |
| 143 | /** Проверяет, что уже создан экземпляр объекта */ |
|
172 | /** Проверяет, что уже создан экземпляр объекта */ | |
| 144 | has(): boolean; |
|
173 | has(): boolean; | |
| 145 |
|
174 | |||
| 146 | get(): T; |
|
175 | get(): T; | |
| 147 |
|
176 | |||
| 148 | initialize(context: IActivationContext<object>): void; |
|
177 | initialize(context: IActivationContext<object>): void; | |
| 149 |
|
178 | |||
| 150 | store(item: T, cleanup?: (item: T) => void): void; |
|
179 | store(item: T, cleanup?: (item: T) => void): void; | |
| 151 | } |
|
180 | } | |
| 152 |
|
181 | |||
| 153 | export type ExtractRequired<T, K extends keyof T = keyof T> = { [p in K as (undefined extends T[p] ? never : p)]-?: T[p] }; |
|
182 | export type ExtractRequired<T, K extends keyof T = keyof T> = { [p in K as (undefined extends T[p] ? never : p)]-?: T[p] }; | |
| 154 |
|
183 | |||
| @@ -1,49 +1,49 | |||||
| 1 | import { FluentConfiguration } from "./FluentConfiguration"; |
|
1 | import { FluentConfiguration } from "./FluentConfiguration"; | |
| 2 | import { IDestroyable } from "./interfaces"; |
|
2 | import { IDestroyable } from "./interfaces"; | |
| 3 |
|
3 | |||
| 4 |
export function fluent<S |
|
4 | export function fluent<S = {}>() { | |
| 5 | return new FluentConfiguration<S>(); |
|
5 | return new FluentConfiguration<S>(); | |
| 6 | } |
|
6 | } | |
| 7 |
|
7 | |||
| 8 | export type key = string | number | symbol; |
|
8 | export type key = string | number | symbol; | |
| 9 |
|
9 | |||
| 10 | export const isKey = (v: unknown): v is key => |
|
10 | export const isKey = (v: unknown): v is key => | |
| 11 | typeof v === "string" || typeof v === "number" || typeof v === "symbol"; |
|
11 | typeof v === "string" || typeof v === "number" || typeof v === "symbol"; | |
| 12 |
|
12 | |||
| 13 | export const isString = (v: unknown): v is string => |
|
13 | export const isString = (v: unknown): v is string => | |
| 14 | typeof v === "string"; |
|
14 | typeof v === "string"; | |
| 15 |
|
15 | |||
| 16 |
|
16 | |||
| 17 | export const isNotNull = <T>(v: T): v is NonNullable<T> => v !== null && v !== undefined; |
|
17 | export const isNotNull = <T>(v: T): v is NonNullable<T> => v !== null && v !== undefined; | |
| 18 |
|
18 | |||
| 19 | export const each = <T extends object>(obj: T, cb: <X extends Extract<keyof T, string>>(v: NonNullable<T[X]>, k: X) => void) => |
|
19 | export const each = <T extends object>(obj: T, cb: <X extends Extract<keyof T, string>>(v: NonNullable<T[X]>, k: X) => void) => | |
| 20 | (Object.keys(obj) as (Extract<keyof T, string>)[]) |
|
20 | (Object.keys(obj) as (Extract<keyof T, string>)[]) | |
| 21 | .forEach(k => { |
|
21 | .forEach(k => { | |
| 22 | const v = obj[k]; |
|
22 | const v = obj[k]; | |
| 23 | isNotNull(v) && cb(v, k); |
|
23 | isNotNull(v) && cb(v, k); | |
| 24 | }); |
|
24 | }); | |
| 25 |
|
25 | |||
| 26 | export const argumentNotNull = (arg: unknown, name: string) => { |
|
26 | export const argumentNotNull = (arg: unknown, name: string) => { | |
| 27 | if (arg === null || arg === undefined) |
|
27 | if (arg === null || arg === undefined) | |
| 28 | throw new Error("The argument " + name + " can't be null or undefined"); |
|
28 | throw new Error("The argument " + name + " can't be null or undefined"); | |
| 29 | }; |
|
29 | }; | |
| 30 |
|
30 | |||
| 31 | export const isPromise = <T = unknown>(val: unknown): val is PromiseLike<T> => |
|
31 | export const isPromise = <T = unknown>(val: unknown): val is PromiseLike<T> => | |
| 32 | isNotNull(val) && typeof (val as PromiseLike<T>).then === "function"; |
|
32 | isNotNull(val) && typeof (val as PromiseLike<T>).then === "function"; | |
| 33 |
|
33 | |||
| 34 | export const isDestroyable = (d: unknown): d is IDestroyable => |
|
34 | export const isDestroyable = (d: unknown): d is IDestroyable => | |
| 35 | isNotNull(d) && typeof (d as IDestroyable).destroy === "function"; |
|
35 | isNotNull(d) && typeof (d as IDestroyable).destroy === "function"; | |
| 36 |
|
36 | |||
| 37 | let _nextOid = 0; |
|
37 | let _nextOid = 0; | |
| 38 | const _oid = typeof Symbol === "function" ? |
|
38 | const _oid = typeof Symbol === "function" ? | |
| 39 | Symbol.for("__implab__oid__") : |
|
39 | Symbol.for("__implab__oid__") : | |
| 40 | "__implab__oid__"; |
|
40 | "__implab__oid__"; | |
| 41 |
|
41 | |||
| 42 | type OidSlot = { [k in typeof _oid]?: string }; |
|
42 | type OidSlot = { [k in typeof _oid]?: string }; | |
| 43 |
|
43 | |||
| 44 | export const oid = <T extends object>(instance: T): string => { |
|
44 | export const oid = <T extends object>(instance: T): string => { | |
| 45 | argumentNotNull(instance, "instance"); |
|
45 | argumentNotNull(instance, "instance"); | |
| 46 | const val = (instance as OidSlot)[_oid]; |
|
46 | const val = (instance as OidSlot)[_oid]; | |
| 47 |
|
47 | |||
| 48 | return val ? val : ((instance as OidSlot)[_oid] = `oid_${++_nextOid}`); |
|
48 | return val ? val : ((instance as OidSlot)[_oid] = `oid_${++_nextOid}`); | |
| 49 | }; No newline at end of file |
|
49 | }; | |
| @@ -1,87 +1,100 | |||||
| 1 | /* eslint max-classes-per-file: ["error", 20] */ |
|
1 | /* eslint max-classes-per-file: ["error", 20] */ | |
| 2 | import { describe, it } from "mocha"; |
|
2 | import { describe, it } from "mocha"; | |
| 3 | import { Container } from "../Container"; |
|
3 | import { Container } from "../Container"; | |
| 4 |
import { Container |
|
4 | import { ContainerBuilder } from "../ContainerBuilder"; | |
|
|
5 | import { ConfigurableKeys, ContainerProvided, ContainerServices, DepsMap, Refs, Resolver } from "../interfaces"; | |||
| 5 | import { fluent } from "../traits"; |
|
6 | import { fluent } from "../traits"; | |
| 6 |
|
7 | |||
| 7 | class Foo { |
|
8 | class Foo { | |
| 8 | foo = "foo"; |
|
9 | foo = "foo"; | |
| 9 | } |
|
10 | } | |
| 10 |
|
11 | |||
| 11 | class Bar { |
|
12 | class Bar { | |
| 12 | bar = "bar"; |
|
13 | bar = "bar"; | |
| 13 |
|
14 | |||
| 14 | constructor(foo?: () => Foo) { } |
|
15 | constructor(foo?: () => Foo) { } | |
| 15 | } |
|
16 | } | |
| 16 |
|
17 | |||
| 17 | interface Services { |
|
18 | interface Services { | |
| 18 | foo: Foo; |
|
19 | foo: Foo; | |
| 19 |
|
20 | |||
| 20 | bar?: Bar; |
|
21 | bar?: Bar; | |
| 21 |
|
22 | |||
| 22 | baz: Foo; |
|
23 | baz: Foo; | |
|
|
24 | ||||
|
|
25 | container: string; | |||
| 23 | } |
|
26 | } | |
| 24 |
|
27 | |||
| 25 | interface ServicesB { |
|
28 | interface ServicesB { | |
| 26 | // will give errors |
|
29 | // will give errors | |
| 27 | // baz: Bar; |
|
30 | // baz: Bar; | |
| 28 |
|
31 | |||
| 29 | baz: Foo; |
|
32 | baz: Foo; | |
| 30 |
|
33 | |||
| 31 | zoo?: Foo; |
|
34 | zoo?: Foo; | |
| 32 | } |
|
35 | } | |
| 33 |
|
36 | |||
| 34 | declare const resolver: Resolver<Services>; |
|
37 | declare const resolver: Resolver<Services>; | |
| 35 |
|
38 | |||
| 36 | const foo = resolver("foo", {lazy: true}); |
|
39 | const foo = resolver("foo", {lazy: true}); | |
| 37 |
|
40 | |||
| 38 |
const mmap = <X extends DepsMap<Services |
|
41 | const mmap = <X extends DepsMap<Services>>(m: X) => {}; | |
|
|
42 | ||||
|
|
43 | declare const refs: Refs<Services>; | |||
| 39 |
|
44 | |||
| 40 | const refs: Refs<Services> = {name: "bar", default: undefined}; |
|
45 | if (refs && refs.name === "foo") { | |
|
|
46 | refs.default; | |||
|
|
47 | } | |||
|
|
48 | ||||
|
|
49 | declare const x: ContainerServices<Services>; | |||
|
|
50 | ||||
|
|
51 | x.container.resolve("container"); | |||
|
|
52 | ||||
| 41 |
|
53 | |||
| 42 | mmap({ |
|
54 | mmap({ | |
| 43 | fooz: {name: "foo", lazy: false, default: undefined }, |
|
55 | fooz: {name: "foo", lazy: false, default: undefined }, | |
| 44 | ooz: "bar" |
|
56 | ooz: "bar" | |
| 45 | }); |
|
57 | }); | |
| 46 |
|
58 | |||
| 47 | interface SharedServices { |
|
59 | interface SharedServices { | |
| 48 | foo: Foo; |
|
60 | foo: Foo; | |
| 49 |
|
61 | |||
| 50 | bar?: Bar; |
|
62 | bar?: Bar; | |
| 51 |
|
63 | |||
| 52 | baz: Bar; |
|
64 | baz: Bar; | |
| 53 | } |
|
65 | } | |
| 54 |
|
66 | |||
| 55 | const config = fluent() |
|
67 | const config = fluent() | |
| 56 | .declare<Services>() |
|
68 | .declare<Services>() | |
| 57 | .declare<ServicesB>() |
|
69 | .declare<ServicesB>() | |
| 58 | .register({ |
|
70 | .register({ | |
|
|
71 | zoo: it => {}, | |||
| 59 | bar: it => it |
|
72 | bar: it => it | |
| 60 | .lifetime("context") // тип активации, время жизни |
|
73 | .lifetime("context") // тип активации, время жизни | |
| 61 | .wants({ |
|
74 | .wants({ | |
| 62 | zoo: "zoo", // зависимость |
|
75 | zoo: "zoo", // зависимость | |
| 63 | bar: "bar", |
|
76 | bar: "bar", | |
| 64 |
|
77 | |||
| 65 | $zoo: { name: "foo", lazy: true, } // отложенная активация, |
|
78 | $zoo: { name: "foo", lazy: true, } // отложенная активация, | |
| 66 | //фабричный метод |
|
79 | //фабричный метод | |
| 67 | }) |
|
80 | }) | |
| 68 | .wants({ |
|
81 | .wants({ | |
| 69 | zoom: "bar" |
|
82 | zoom: "bar" | |
| 70 | }) |
|
83 | }) | |
| 71 | .factory(({ $zoo, zoo }) => // фабрика получает объект с именованными зависимостями |
|
84 | .factory(({ $zoo, zoo }) => // фабрика получает объект с именованными зависимостями | |
| 72 | // удобно для деструктурирования |
|
85 | // удобно для деструктурирования | |
| 73 | new Bar($zoo) // создается экземпляр сервиса |
|
86 | new Bar($zoo) // создается экземпляр сервиса | |
| 74 | ), |
|
87 | ), | |
| 75 | foo: it => it.factory(() => new Foo()), |
|
88 | foo: it => it.factory(() => new Foo()), | |
| 76 | baz: it => it.value(new Foo()) |
|
89 | baz: it => it.value(new Foo()) | |
| 77 | }) |
|
90 | }) | |
| 78 | .done({}); |
|
91 | .done({}); | |
| 79 |
|
92 | |||
| 80 |
declare const container: Container< |
|
93 | declare const container: ContainerBuilder<{}>; | |
| 81 | const c2 = config.configure(container); |
|
94 | const c2 = config.configure(container); | |
| 82 |
|
95 | |||
| 83 | c2.resolve("foo"); |
|
96 | c2.resolve("foo"); | |
| 84 |
|
97 | |||
| 85 | declare const m :ContainerServices<{foo: Foo}>["container"]; |
|
98 | declare const m :ContainerServices<{foo: Foo}>["container"]; | |
| 86 |
|
99 | |||
| 87 | m.resolve("container").resolve("container"); No newline at end of file |
|
100 | m.resolve("container").resolve("container").resolve("foo"); No newline at end of file | |
General Comments 0
You need to be logged in to leave comments.
Login now
