| @@ -1,143 +1,154 | |||||
| 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 extends object> { |
|
10 | export interface Resolver<S extends object> { | |
| 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<K extends key, SK extends key> = { [k in K]: SK | Ref<SK, boolean, unknown> }; |
|
30 | export type DepsMap<S extends object> = { | |
|
|
31 | [k in key]: Refs<S> | |||
|
|
32 | }; | |||
|
|
33 | ||||
|
|
34 | export type Refs<S extends object> = { | |||
|
|
35 | [k in keyof S]: Ref<k, boolean, S[k]>; | |||
|
|
36 | }[keyof S] | keyof S; | |||
|
|
37 | ||||
|
|
38 | export type Ref<K extends key, L extends boolean, D> = { name: K, lazy?: L, default?: D | null }; | |||
| 31 |
|
39 | |||
| 32 | export type Ref<K extends key, L extends boolean, D> = { name: K, lazy?: L } | { name: K, lazy?: L, default: D }; |
|
40 | export type Lazy<T, L extends boolean> = L extends true ? () => T : T; | |
|
|
41 | ||||
|
|
42 | export type InferDefault<T> = T extends { default: infer D } ? D : never; | |||
| 33 |
|
43 | |||
| 34 |
export type Resolve |
|
44 | export type Resolve<S extends object, R> = | |
| 35 | L extends true ? () => NonNullable<S[K]> | (unknown extends D ? never : D) : NonNullable<S[K]> | (unknown extends D ? never : D); |
|
45 | R extends keyof S ? NonNullable<S[R]> : | |
|
|
46 | R extends Ref<infer K, infer L, unknown> ? | |||
|
|
47 | K extends keyof S ? Lazy<NonNullable<S[K]> | InferDefault<R>, L> : | |||
|
|
48 | never: | |||
|
|
49 | never; | |||
| 36 |
|
50 | |||
| 37 | export interface IDescriptorBuilder<S extends object, T, R extends object, O extends keyof S> { |
|
51 | export interface IDescriptorBuilder<S extends object, T, R extends object, O extends keyof S> { | |
| 38 |
|
52 | |||
| 39 | /** |
|
53 | /** | |
| 40 | * |
|
54 | * | |
| 41 | * @param f |
|
55 | * @param f | |
| 42 | */ |
|
56 | */ | |
| 43 | factory(f: (refs: R) => T): void; |
|
57 | factory(f: (refs: R) => T): void; | |
| 44 |
|
58 | |||
| 45 |
wants<X extends DepsMap< |
|
59 | wants<X extends DepsMap<S> & Record<keyof R & keyof X, never>>(refs: X): | |
| 46 | IDescriptorBuilder<S, T, R & { |
|
60 | IDescriptorBuilder<S, T, R & { | |
| 47 | [k in keyof X]: |
|
61 | [k in keyof X]: Resolve<S, X[k]>; | |
| 48 | X[k] extends keyof S ? NonNullable<S[X[k]]> : |
|
|||
| 49 | X[k] extends Ref<infer K, infer L, infer D> ? Resolved<S, K & keyof S, L, D> : |
|
|||
| 50 | never |
|
|||
| 51 | }, O> |
|
62 | }, O> | |
| 52 |
|
63 | |||
| 53 | override<K extends O>(name: K, builder: RegistrationBuilder<S, NonNullable<S[K]>>): this; |
|
64 | override<K extends O>(name: K, builder: RegistrationBuilder<S, NonNullable<S[K]>>): this; | |
| 54 | override<K extends O>(services: { [name in K]: RegistrationBuilder<S, NonNullable<S[K]>> }): this; |
|
65 | override<K extends O>(services: { [name in K]: RegistrationBuilder<S, NonNullable<S[K]>> }): this; | |
| 55 |
|
66 | |||
| 56 | lifetime(lifetime: "singleton", typeId: string | number | object): this; |
|
67 | lifetime(lifetime: "singleton", typeId: string | number | object): this; | |
| 57 | lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this; |
|
68 | lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this; | |
| 58 |
|
69 | |||
| 59 | cleanup(cb: (item: T) => void): this; |
|
70 | cleanup(cb: (item: T) => void): this; | |
| 60 |
|
71 | |||
| 61 | value(v: T): void; |
|
72 | value(v: T): void; | |
| 62 | } |
|
73 | } | |
| 63 |
|
74 | |||
| 64 | export type RegistrationBuilder<S extends object, T> = (d: IDescriptorBuilder<S, T, object, ConfigurableKeys<S>>) => void; |
|
75 | export type RegistrationBuilder<S extends object, T> = (d: IDescriptorBuilder<S, T, object, ConfigurableKeys<S>>) => void; | |
| 65 |
|
76 | |||
| 66 | export type RegistrationBuildersMap<S extends Configurable<S>, K extends keyof S = keyof S> = { |
|
77 | export type RegistrationBuildersMap<S extends Configurable<S>, K extends keyof S = keyof S> = { | |
| 67 | [k in K]-?: RegistrationBuilder<ContainerServices<S>, NonNullable<S[k]>> |
|
78 | [k in K]-?: RegistrationBuilder<ContainerServices<S>, NonNullable<S[k]>> | |
| 68 | }; |
|
79 | }; | |
| 69 |
|
80 | |||
| 70 | export interface Descriptor<S extends object, T> { |
|
81 | export interface Descriptor<S extends object, T> { | |
| 71 | activate(context: IActivationContext<S>): T; |
|
82 | activate(context: IActivationContext<S>): T; | |
| 72 | } |
|
83 | } | |
| 73 |
|
84 | |||
| 74 | export interface IActivationContext<S extends object> extends ServiceLocator<S> { |
|
85 | export interface IActivationContext<S extends object> extends ServiceLocator<S> { | |
| 75 | createLifetime<T>(): ILifetime<T>; |
|
86 | createLifetime<T>(): ILifetime<T>; | |
| 76 |
|
87 | |||
| 77 | createContainerLifetime<T>(): ILifetime<T>; |
|
88 | createContainerLifetime<T>(): ILifetime<T>; | |
| 78 | } |
|
89 | } | |
| 79 |
|
90 | |||
| 80 | export type RegistrationMap<S extends object, K extends keyof S = keyof S> = { |
|
91 | export type RegistrationMap<S extends object, K extends keyof S = keyof S> = { | |
| 81 | [k in K]-?: Descriptor<S, S[k]>; |
|
92 | [k in K]-?: Descriptor<S, S[k]>; | |
| 82 | }; |
|
93 | }; | |
| 83 |
|
94 | |||
| 84 | export interface ContainerProvided<S extends Configurable<S>> { |
|
95 | export interface ContainerProvided<S extends Configurable<S>> { | |
| 85 | container: ServiceLocator<ContainerServices<S>>; |
|
96 | container: ServiceLocator<ContainerServices<S>>; | |
| 86 |
|
97 | |||
| 87 | childContainer: IContainerBuilder<S>; |
|
98 | childContainer: IContainerBuilder<S>; | |
| 88 | } |
|
99 | } | |
| 89 |
|
100 | |||
| 90 | export type Configurable<S> = { [k in keyof S & (keyof ContainerProvided<never>)]: never; }; |
|
101 | export type Configurable<S> = { [k in keyof S & (keyof ContainerProvided<never>)]: never; }; | |
| 91 |
|
102 | |||
| 92 | export type ProvidedKeys = keyof ContainerProvided<never>; |
|
103 | export type ProvidedKeys = keyof ContainerProvided<never>; | |
| 93 |
|
104 | |||
| 94 | export type ContainerServices<S extends Configurable<S>> = S & ContainerProvided<S>; |
|
105 | export type ContainerServices<S extends Configurable<S>> = S & ContainerProvided<S>; | |
| 95 |
|
106 | |||
| 96 | export type ConfigurableKeys<S extends object> = Exclude<keyof S, ProvidedKeys>; |
|
107 | export type ConfigurableKeys<S extends object> = Exclude<keyof S, ProvidedKeys>; | |
| 97 |
|
108 | |||
| 98 | export type ConfigurableServices<S extends object> = Pick<S, ConfigurableKeys<S>>; |
|
109 | export type ConfigurableServices<S extends object> = Pick<S, ConfigurableKeys<S>>; | |
| 99 |
|
110 | |||
| 100 | export type ContainerKeys<S extends Configurable<S>> = keyof S | keyof ContainerProvided<never>; |
|
111 | export type ContainerKeys<S extends Configurable<S>> = keyof S | keyof ContainerProvided<never>; | |
| 101 |
|
112 | |||
| 102 | export interface ServiceLocator<S extends object> { |
|
113 | export interface ServiceLocator<S extends object> { | |
| 103 | resolve<K extends keyof S>(name: K): NonNullable<S[K]>; |
|
114 | resolve<K extends keyof S>(name: K): NonNullable<S[K]>; | |
| 104 | resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T; |
|
115 | resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T; | |
| 105 | } |
|
116 | } | |
| 106 |
|
117 | |||
| 107 | export interface LifetimeContainer { |
|
118 | export interface LifetimeContainer { | |
| 108 | createLifetime<T>(): ILifetime<T>; |
|
119 | createLifetime<T>(): ILifetime<T>; | |
| 109 | } |
|
120 | } | |
| 110 |
|
121 | |||
| 111 | export interface ServiceContainer<S extends Configurable<S>> extends |
|
122 | export interface ServiceContainer<S extends Configurable<S>> extends | |
| 112 | ServiceLocator<ContainerServices<S>>, |
|
123 | ServiceLocator<ContainerServices<S>>, | |
| 113 | IDestroyable { |
|
124 | IDestroyable { | |
| 114 |
|
125 | |||
| 115 | createChildContainer(): IContainerBuilder<S>; |
|
126 | createChildContainer(): IContainerBuilder<S>; | |
| 116 | } |
|
127 | } | |
| 117 |
|
128 | |||
| 118 | export interface IContainerBuilder<S extends Configurable<S>> { |
|
129 | export interface IContainerBuilder<S extends Configurable<S>> { | |
| 119 | createServiceBuilder<K extends keyof S>(name: K): IDescriptorBuilder<S, NonNullable<S[K]>, object, keyof S>; |
|
130 | createServiceBuilder<K extends keyof S>(name: K): IDescriptorBuilder<S, NonNullable<S[K]>, object, keyof S>; | |
| 120 |
|
131 | |||
| 121 | build(): ServiceContainer<S>; |
|
132 | build(): ServiceContainer<S>; | |
| 122 | } |
|
133 | } | |
| 123 |
|
134 | |||
| 124 |
|
135 | |||
| 125 | export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call"; |
|
136 | export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call"; | |
| 126 |
|
137 | |||
| 127 | /** |
|
138 | /** | |
| 128 | * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет |
|
139 | * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет | |
| 129 | * свой собственный объект `ILifetime`, который создается при первой активации |
|
140 | * свой собственный объект `ILifetime`, который создается при первой активации | |
| 130 | */ |
|
141 | */ | |
| 131 | export interface ILifetime<T> { |
|
142 | export interface ILifetime<T> { | |
| 132 | /** Проверяет, что уже создан экземпляр объекта */ |
|
143 | /** Проверяет, что уже создан экземпляр объекта */ | |
| 133 | has(): boolean; |
|
144 | has(): boolean; | |
| 134 |
|
145 | |||
| 135 | get(): T; |
|
146 | get(): T; | |
| 136 |
|
147 | |||
| 137 | initialize(context: IActivationContext<object>): void; |
|
148 | initialize(context: IActivationContext<object>): void; | |
| 138 |
|
149 | |||
| 139 | store(item: T, cleanup?: (item: T) => void): void; |
|
150 | store(item: T, cleanup?: (item: T) => void): void; | |
| 140 | } |
|
151 | } | |
| 141 |
|
152 | |||
| 142 | export type ExtractRequired<T, K extends keyof T = keyof T> = { [p in K as (undefined extends T[p] ? never : p)]-?: T[p] }; |
|
153 | export type ExtractRequired<T, K extends keyof T = keyof T> = { [p in K as (undefined extends T[p] ? never : p)]-?: T[p] }; | |
| 143 |
|
154 | |||
| @@ -1,70 +1,87 | |||||
| 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 { ContainerServices } from "../interfaces"; |
|
4 | import { ContainerServices, DepsMap, Refs, Resolver } from "../interfaces"; | |
| 5 | import { fluent } from "../traits"; |
|
5 | import { fluent } from "../traits"; | |
| 6 |
|
6 | |||
| 7 | class Foo { |
|
7 | class Foo { | |
| 8 | foo = "foo"; |
|
8 | foo = "foo"; | |
| 9 | } |
|
9 | } | |
| 10 |
|
10 | |||
| 11 | class Bar { |
|
11 | class Bar { | |
| 12 | bar = "bar"; |
|
12 | bar = "bar"; | |
| 13 |
|
13 | |||
| 14 | constructor(foo?: () => Foo) { } |
|
14 | constructor(foo?: () => Foo) { } | |
| 15 | } |
|
15 | } | |
| 16 |
|
16 | |||
| 17 | interface Services { |
|
17 | interface Services { | |
| 18 | foo: Foo; |
|
18 | foo: Foo; | |
| 19 |
|
19 | |||
| 20 | bar?: Bar; |
|
20 | bar?: Bar; | |
| 21 |
|
21 | |||
| 22 | baz: Foo; |
|
22 | baz: Foo; | |
| 23 | } |
|
23 | } | |
| 24 |
|
24 | |||
| 25 | interface ServicesB { |
|
25 | interface ServicesB { | |
| 26 | // will give errors |
|
26 | // will give errors | |
| 27 | // baz: Bar; |
|
27 | // baz: Bar; | |
| 28 |
|
28 | |||
| 29 | baz: Foo; |
|
29 | baz: Foo; | |
| 30 |
|
30 | |||
| 31 | zoo?: Foo; |
|
31 | zoo?: Foo; | |
| 32 | } |
|
32 | } | |
| 33 |
|
33 | |||
|
|
34 | declare const resolver: Resolver<Services>; | |||
|
|
35 | ||||
|
|
36 | const foo = resolver("foo", {lazy: true}); | |||
|
|
37 | ||||
|
|
38 | const mmap = <X extends DepsMap<Services, string>>(m: X) => {}; | |||
|
|
39 | ||||
|
|
40 | const refs: Refs<Services> = {name: "bar", default: undefined}; | |||
|
|
41 | ||||
|
|
42 | mmap({ | |||
|
|
43 | fooz: {name: "foo", lazy: false, default: undefined }, | |||
|
|
44 | ooz: "bar" | |||
|
|
45 | }); | |||
|
|
46 | ||||
| 34 | interface SharedServices { |
|
47 | interface SharedServices { | |
| 35 | foo: Foo; |
|
48 | foo: Foo; | |
| 36 |
|
49 | |||
| 37 | bar?: Bar; |
|
50 | bar?: Bar; | |
| 38 |
|
51 | |||
| 39 | baz: Bar; |
|
52 | baz: Bar; | |
| 40 | } |
|
53 | } | |
| 41 |
|
54 | |||
| 42 | const config = fluent() |
|
55 | const config = fluent() | |
| 43 | .declare<Services>() |
|
56 | .declare<Services>() | |
| 44 | .declare<ServicesB>() |
|
57 | .declare<ServicesB>() | |
| 45 | .register({ |
|
58 | .register({ | |
| 46 | bar: it => it |
|
59 | bar: it => it | |
| 47 | .lifetime("context") // тип активации, время жизни |
|
60 | .lifetime("context") // тип активации, время жизни | |
| 48 | .wants({ |
|
61 | .wants({ | |
| 49 | zoo: "zoo", // зависимость |
|
62 | zoo: "zoo", // зависимость | |
|
|
63 | bar: "bar", | |||
| 50 |
|
|
64 | ||
| 51 |
zoo |
|
65 | $zoo: { name: "foo", lazy: true, } // отложенная активация, | |
| 52 | //фабричный метод |
|
66 | //фабричный метод | |
| 53 | }) |
|
67 | }) | |
| 54 | .factory(({ zoo$ }) => // фабрика получает объект с именованными зависимостями |
|
68 | .wants({ | |
|
|
69 | zoom: "bar" | |||
|
|
70 | }) | |||
|
|
71 | .factory(({ $zoo, zoo }) => // фабрика получает объект с именованными зависимостями | |||
| 55 | // удобно для деструктурирования |
|
72 | // удобно для деструктурирования | |
| 56 |
new Bar(zoo |
|
73 | new Bar($zoo) // создается экземпляр сервиса | |
| 57 | ), |
|
74 | ), | |
| 58 | foo: it => it.factory(() => new Foo()), |
|
75 | foo: it => it.factory(() => new Foo()), | |
| 59 | baz: it => it.value(new Foo()) |
|
76 | baz: it => it.value(new Foo()) | |
| 60 | }) |
|
77 | }) | |
| 61 | .done({}); |
|
78 | .done({}); | |
| 62 |
|
79 | |||
| 63 | declare const container: Container<object>; |
|
80 | declare const container: Container<object>; | |
| 64 | const c2 = config.configure(container); |
|
81 | const c2 = config.configure(container); | |
| 65 |
|
82 | |||
| 66 | c2.resolve("foo"); |
|
83 | c2.resolve("foo"); | |
| 67 |
|
84 | |||
| 68 | declare const m :ContainerServices<{foo: Foo}>["container"]; |
|
85 | declare const m :ContainerServices<{foo: Foo}>["container"]; | |
| 69 |
|
86 | |||
| 70 | m.resolve("container").resolve("container"); No newline at end of file |
|
87 | m.resolve("container").resolve("container"); | |
General Comments 0
You need to be logged in to leave comments.
Login now
