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