##// END OF EJS Templates
WIP lifetime services
cin -
r12:94f233c23aa4 default
parent child
Show More
@@ -1,67 +1,66
1 1 import { Container } from "./Container";
2 2 import { DescriptorBuilder } from "./DescriptorBuilder";
3 3 import { Descriptor, IContainerBuilder, IDescriptorBuilder, DescriptorMap, ServiceLocator, ILifetime, IDestroyable } from "./interfaces";
4 4 import { emptyLifetime, LifetimeManager } from "./LifetimeManager";
5 5 import { isDestroyable, prototype } from "./traits";
6 6
7 7 /**
8 8 * Container builder used to prepare service descriptors and create a IoC container
9 9 */
10 10 export class ContainerBuilder<S, U extends keyof S> implements
11 11 IContainerBuilder<S, U> {
12 12
13 13 private _pending = 1;
14 14
15 15 private readonly _services: DescriptorMap<S>;
16 16
17 17 private readonly _lifetimeManager = new LifetimeManager();
18 18
19 19 private readonly _lifetime: ILifetime<IDestroyable>;
20 20
21 21 constructor(parentServices: DescriptorMap<S> | null = null, lifetime?: ILifetime<IDestroyable>) {
22 22 this._services = prototype(parentServices);
23 23 this._lifetimeManager = new LifetimeManager();
24 24 this._lifetime = lifetime ?? emptyLifetime();
25 25 }
26 26 createServiceBuilder<K extends U>(name: K):
27 27 IDescriptorBuilder<S, S[K], Record<never, never>, U> {
28 28
29 29 return new DescriptorBuilder(this._lifetimeManager, this._register(name), this._fail);
30 30
31 31 }
32 32
33 33 build(): ServiceLocator<S> {
34 34 this._assertBuilding();
35 35 if (!this._complete())
36 36 throw new Error("The configuration didn't complete.");
37 37
38 const lifetime = this._lifetime;
38 const {remove, store} = this._lifetime(null);
39 39
40 const detach = isDestroyable(lifetime) ? () => lifetime.destroy() : () => void (0);
40 const container = new Container(this._services, this._lifetimeManager, remove);
41 41
42 const container = new Container(this._services, this._lifetimeManager, detach);
43 lifetime.store(container);
42 store(container);
44 43
45 44 return container;
46 45 }
47 46
48 47 private readonly _register = <K extends U>(name: K) =>
49 48 (descriptor: Descriptor<S, S[K]>) => {
50 49 this._complete();
51 50 this._services[name] = descriptor;
52 51 };
53 52
54 53 private readonly _fail = (ex: unknown) => {
55 54 throw ex;
56 55 };
57 56
58 57 private _assertBuilding() {
59 58 if (!this._pending)
60 59 throw new Error("The descriptor builder is finalized");
61 60 }
62 61
63 62 private _complete() {
64 63 return !(--this._pending);
65 64 }
66 65
67 66 } No newline at end of file
@@ -1,93 +1,97
1 import { ActivationError } from "./ActivationError";
2 1 import { Descriptor, ILifetime, DepsMap, IActivationContext, DescriptorMap } from "./interfaces";
3 2 import { each, key } from "./traits";
4 3
5 4 export interface DescriptorImplArgs<S, T> {
6 5 lifetime: ILifetime<T>;
7 6
8 7 factory: (refs: Record<key, unknown>) => NonNullable<T>;
9 8
10 9 cleanup?: (item: NonNullable<T>) => void;
11 10
12 11 overrides?: DescriptorMap<S>;
13 12
14 13 dependencies?: DepsMap<S>;
15 14 }
16 15
17 16
18 17 export class DescriptorImpl<S, T> implements Descriptor<S, T> {
19 18
20 19 private readonly _overrides?: DescriptorMap<S>;
21 20
22 21 private readonly _lifetime: ILifetime<T>;
23 22
24 23 private readonly _factory: (refs: Record<key, unknown>) => NonNullable<T>;
25 24
26 25 private readonly _cleanup?: (item: NonNullable<T>) => void;
27 26
28 27 private readonly _deps?: DepsMap<S>;
29 28
30 29 readonly hasOverrides: boolean;
31 30
32 31 constructor({ lifetime, factory, cleanup, overrides, dependencies }: DescriptorImplArgs<S, T>) {
33 32 this._lifetime = lifetime;
34 33 this._factory = factory;
35 34 if (cleanup)
36 35 this._cleanup = cleanup;
37 36 if (overrides)
38 37 this._overrides = overrides;
39 38 if (dependencies)
40 39 this._deps = dependencies;
41 40
42 41 this.hasOverrides = !!overrides;
43 42 }
44 43
45 44 activate(context: IActivationContext<S>): NonNullable<T> {
46 45
47 if (this._lifetime.has())
48 return this._lifetime.get();
46 const { has, get, initialize, store } = this._lifetime(context);
49 47
50 this._lifetime.initialize(context);
48 if (has())
49 return get();
50
51 initialize();
51 52
52 53 if (this._overrides)
53 54 each(this._overrides, (v, k) => context.register(k, v));
54 55
55 56 const resolve = <K extends keyof S>({ name, lazy, ...opts }: { name: K; lazy?: boolean; default?: S[K] | null; }) => {
56 57 if (lazy) {
57 58 return "default" in opts ?
58 59 () => context.resolve(name, opts.default) :
59 60 () => context.resolve(name);
60 61 } else {
61 62 return "default" in opts ?
62 63 context.resolve(name, opts.default) :
63 64 context.resolve(name);
64 65 }
65 66 };
66 67
67 68 const deps = this._deps;
68 69
69 70 const refs = deps ?
70 71 Object.keys(deps)
71 72 .map(k => {
72 73 const ref = deps[k];
73 74 return typeof ref !== "object" ?
74 75 { [k]: resolve({ name: ref }) } :
75 76 { [k]: resolve(ref) };
76 77 })
77 78 .reduce((a, p) => ({ ...a, ...p }), {}) :
78 79 {};
79 80
80 81 try {
82 // call the factory method
81 83 const instance = (0,this._factory)(refs);
82 this._lifetime.store(instance, this._cleanup);
84
85 // store the instance
86 store(instance, this._cleanup);
83 87 return instance;
84 88 } catch(err) {
85 89 context.fail(err);
86 90 }
87 91 }
88 92
89 93
90 94 toString() {
91 95 return `[object DescriptorImpl, lifetime=${String(this._lifetime)}]`;
92 96 }
93 97 }
@@ -1,252 +1,170
1 import { IActivationContext, ILifetime, ILifetimeContext, ILifetimeManager, ILifetimeSlot } from "./interfaces";
2 import { ActivationContext } from "./ActivationContext";
1 import { IDestroyable, ILifetimeContext, ILifetimeManager, ILifetimeSlot } from "./interfaces";
3 2 import { argumentNotNull, isDestroyable } from "./traits";
4 3
5 4 const safeCall = (item: () => void) => {
6 5 try {
7 6 item();
8 7 } catch {
9 8 // silence!
10 9 }
11 10 };
12 11
13 const noop = () => {};
12 const noop = () => { };
14 13
15 const fail = (message: string) => {
14 const fail = (message: string) => (): never => {
16 15 throw new Error(message);
17 16 };
18 17
19 18 const _emptySlot = Object.freeze({
20 19 has: () => false,
21 20
22 21 initialize: noop,
23 22
24 23 get: fail("The specified item isn't registered with a lifetime manager"),
25 24
26 25 store: noop,
27 26
28 remove: noop
29 });
30
31 const _unknonwSlot = Object.freeze({
32 has: () => false,
27 remove: noop,
33 28
34 initialize: () => {
35 throw new Error("Can't call initialize on the unknown lifetime object");
36 },
37 get: () => {
38 throw new Error("The lifetime object isn't initialized");
39 },
40 store: () => {
41 throw new Error("Can't store a value in the unknown lifetime object");
42 }
29 cleanup: noop,
43 30 });
44 31
32 const _destroy = (item: IDestroyable) => item.destroy();
45 33
46 const pendingSlot = <T>(store: (item: T) => void) => ({
34 const _makeCleanup = <T>(value: T, cleanup?: (item: T) => void) =>
35 cleanup ? () => cleanup(value) :
36 isDestroyable(value) ? () => _destroy(value) :
37 noop;
38
39 const newSlot = <T>(put: (item: ILifetimeSlot<T>) => void, remove: () => void): ILifetimeSlot<T> => ({
47 40 has: () => false,
48 41
49 get: () => {
50 throw new Error("The value in this slot doesn't exist");
51 },
42 initialize: () => put(pendingSlot(put, remove)),
43
44 get: fail("The slot doesn't hold a value"),
52 45
53 initialize: () => {
54 throw new Error("Cyclic reference detected");
55 },
46 store: (value, cleanup) => put(valueSlot(value, cleanup, remove)),
56 47
57 store
48 remove: noop,
49
50 cleanup: noop,
58 51 });
59 52
60 const valueSlot = <T>(value: T, cleanup: (item: T) => void) => ({
53 const pendingSlot = <T>(put: (item: ILifetimeSlot<T>) => void, remove: () => void): ILifetimeSlot<T> => ({
54 has: () => false,
55
56 get: fail("The value in this slot doesn't exist"),
57
58 initialize: fail("Cyclic reference detected"),
59
60 store: (value, cleanup) => put(valueSlot(value, cleanup, remove)),
61
62 remove,
63
64 cleanup: noop
65 });
66
67 const valueSlot = <T>(value: T, cleanup: ((item: T) => void) | undefined, remove: () => void) => ({
61 68 has: () => true,
62 69
63 70 get: () => value,
64 71
65 initialize: () => {
66 throw new Error("The slot already has a value");
67 },
72 initialize: fail("The slot already has a value"),
73
74 store: fail("The slot already has a value"),
68 75
69 store: () => {
70 throw new Error("The slot already has a value");
71 }
76 cleanup: _makeCleanup(value, cleanup),
77
78 remove: remove
72 79 });
73 80
74 const singletons: { [K: string]: unknown } = {};
75
76 81 export class LifetimeManager implements ILifetimeManager {
77 82 private _destroyed = false;
78 83
79 84 private readonly _slots: Record<string, ILifetimeSlot<unknown>> = {};
80 85
81 slot<T>(cookie: string): ILifetimeSlot<T> {
86 slot<T>(cookie: string | number): ILifetimeSlot<T> {
82 87 if (cookie in this._slots)
83 88 return this._slots[cookie] as ILifetimeSlot<T>;
84 89
85 const store = (item: T, cleanup?: (item: T) => void) => {
86 this._assertNotDestroyed();
87 this._slots[cookie] = valueSlot(
88 item,
89 cleanup ?? isDestroyable(item) ? () => item.destroy() : noop
90 );
91 };
92
93 return {
94 has: () => false,
95 get: () => {
96 throw new Error("The value isn't stored in this slot");
97 },
98 store,
99 initialize: () => {
100 this._assertNotDestroyed();
101 this._slots[cookie] = pendingSlot(store);
102 }
103 };
90 return newSlot<T>(this._put(cookie), this._remove(cookie));
104 91 }
105 92
106 remove(cookie: string) {
107 delete this._slots[cookie];
108 }
93 private readonly _put = (id: string | number) => <T>(slot: ILifetimeSlot<T>) => {
94 this._assertNotDestroyed();
95 this._slots[id] = slot as ILifetimeSlot<unknown>;
96 };
97
98 private readonly _remove = (id: string | number) => () => {
99 this._assertNotDestroyed();
100 delete this._slots[id];
101 };
109 102
110 103 private _assertNotDestroyed() {
111 104 if (this._destroyed)
112 105 throw new Error("The lifetime manager is destroyed");
113 106
114 107 }
115 108
116 109 destroy() {
117 110 if (!this._destroyed) {
118 111 this._destroyed = true;
119 Object.values(this._slots).forEach(({clean}) => )
112 Object.values(this._slots).forEach(({ cleanup }) => safeCall(cleanup));
120 113 }
121 114 }
122 115
123 116 }
124 117
125 export const emptyLifetime = <T>(): ILifetime<T> => {
126 return _emptyLifetime;
127 };
118 export const emptyLifetime = <T>() => () => _emptySlot as ILifetimeSlot<T>;
128 119
129 export const hierarchyLifetime = <T>(): ILifetime<T> => {
130 // TODO: вот здесь ошибка, при первой активации сервиса будет получен и
131 // привязан lifetime из дочернего контейнера, при активации через второй
132 // дочерний контейнера это приведет к ошибке, точнее будет взят экземпляр
133 // из первого контейнера.
134 let _lifetime: ILifetime<T> = _unknownLifetime;
135 return {
136 initialize(context: ILifetimeContext) {
137 if (_lifetime !== _unknownLifetime)
138 throw new Error("Cyclic reference activation detected");
120 let nextId = 1;
139 121
140 _lifetime = context.createContainerLifetime<T>();
141 },
142 get() {
143 return _lifetime.get();
144 },
145 has() {
146 return _lifetime.has();
147 },
148 store(item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void) {
149 return _lifetime.store(item, cleanup);
150 },
151 toString() {
152 return `[object HierarchyLifetime, has=${String(this.has())}]`;
153 }
154 };
122 export const hierarchyLifetime = <T>() => {
123 const slotId = nextId++;
124 return (context: ILifetimeContext) =>
125 context.containerSlot<T>(slotId);
155 126 };
156 127
157 128 /**
158 129 * Creates a lifetime instance bound to the current activation context. This
159 130 * lifetime will store the service instance per activation context. Every
160 131 * top level service resolution will create a new activation context. This
161 132 * context is propagated to subsequent service resolution thus all services
162 133 * with context lifetime will be shared among their consumers.
163 134 *
164 135 * @returns The instance of the lifetime.
165 136 */
166 export const contextLifetime = <T>(): ILifetime<T> => {
167 let _lifetime: ILifetime<T> = _unknownLifetime;
168 return {
169 initialize(context: ILifetimeContext) {
170 if (_lifetime !== _unknownLifetime)
171 throw new Error("Cyclic reference detected");
172 _lifetime = context.createLifetime();
173 },
174 get() {
175 return _lifetime.get();
176 },
177 has() {
178 return _lifetime.has();
179 },
180 store(item: NonNullable<T>) {
181 _lifetime.store(item);
182 },
183 toString() {
184 return `[object ContextLifetime, has=${String(this.has())}]`;
185 }
186 };
137 export const contextLifetime = <T>() => {
138 const slotId = nextId++;
139
140 return (context: ILifetimeContext) =>
141 context.contextSlot<T>(slotId);
187 142 };
188 143
144 const singletons = new LifetimeManager();
145
189 146 /**
190 147 * Creates the lifetime for the service which will allow existence only one
191 148 * instance with the specified {@linkcode typeId}. If there will be created
192 149 * several lifetime instances with same `typeId` in the runtime, they will
193 150 * share the same service instance.
194 151 *
195 152 * @param typeId The identified for the global instance, usually this is a
196 153 * fully qualified class name
197 154 * @returns The lifetime instance
198 155 */
199 export const singletonLifetime = <T>(typeId: string): ILifetime<T> => {
156 export const singletonLifetime = <T>(typeId: string) => {
200 157 argumentNotNull(typeId, "typeId");
201 let pending = false;
202 return {
203 has() {
204 return typeId in singletons;
205 },
206 get() {
207 if (!this.has())
208 throw new Error(`The instance ${typeId} doesn't exists`);
209 return singletons[typeId] as NonNullable<T>;
210 },
211 initialize() {
212 if (pending)
213 throw new Error("Cyclic reference detected");
214 pending = true;
215 },
216 store(item: NonNullable<T>) {
217 singletons[typeId] = item;
218 pending = false;
219 },
220 toString() {
221 return `[object SingletonLifetime, has=${String(this.has())}, typeId=${typeId}]`;
222 }
223 };
158
159 return () => singletons.slot<T>(typeId);
224 160 };
225 161
226 162 /** Creates a lifetime bound to the specified container. Using this lifetime
227 163 * will create a single service instance per the specified container.
228 164 *
229 165 * @param container The container which will manage the lifetime for the service
230 166 */
231 export const containerLifetime = <T>(container: { createLifetime<X>(): ILifetime<X> }) => {
232 let _lifetime: ILifetime<T> = _unknownLifetime;
233 return {
234 initialize() {
235 if (_lifetime !== _unknownLifetime)
236 throw new Error("Cyclic reference detected");
237 _lifetime = container.createLifetime();
238 },
239 get() {
240 return _lifetime.get();
241 },
242 has() {
243 return _lifetime.has();
244 },
245 store(item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void) {
246 _lifetime.store(item, cleanup);
247 },
248 toString() {
249 return `[object ContainerLifetime, has=${String(_lifetime.has())}]`;
250 }
251 };
167 export const containerLifetime = <T>(manager: ILifetimeManager) => {
168 const slotId = nextId++;
169 return () => manager.slot<T>(slotId);
252 170 };
@@ -1,272 +1,265
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> {
11 11 /**
12 12 * Функция для разрешения зависимостей, поддерживает создание фабричных методов,
13 13 * отложенную активацию и значение по-умолчанию для сервисов
14 14 * @template K Ключ сервиса из {@linkcode S}
15 15 * @template O Тип параметра {@linkcode 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; }>(name: K, opts?: O): () => NonNullable<S[K]> | InferDefault<O>;
27 27 <K extends keyof S, O extends { lazy?: false; }>(name: K, opts?: O): NonNullable<S[K]> | InferDefault<O>;
28 28 }
29 29
30 30 export type DepsMap<S> = {
31 31 [k in key]: Refs<S> | keyof S;
32 32 };
33 33
34 34 export type Refs<S> = {
35 35 [k in keyof S]: Ref<k, S[k]>;
36 36 }[keyof S];
37 37
38 38 export type Ref<K extends key, D> = {
39 39 /** The name of the service */
40 40 name: K;
41 41
42 42 /** Make a lazy reference, the resolved dependency will be a function */
43 43 lazy?: boolean;
44 44
45 45 /** The default value for the case where the service isn't defined.
46 46 * When specified the dependency becomes optional, the default value can be
47 47 * `null` or `undefined`
48 48 */
49 49 default?: D | null
50 50 };
51 51
52 52 export type Lazy<T, L extends boolean> = L extends true ? () => T : T;
53 53
54 54 /** Возвращает тип свойства `default` в типе {@link T} */
55 55 export type InferDefault<T> = T extends { default: infer D } ? D : never;
56 56
57 57 export type InferLazy<R> = R extends { lazy: infer L } ?
58 58 L extends true ? true : false :
59 59 false;
60 60 export type Resolve<S, R> =
61 61 R extends keyof S ? NonNullable<S[R]> :
62 62 R extends Ref<infer K, unknown> ?
63 63 K extends keyof S ?
64 64 Lazy<NonNullable<S[K]> | InferDefault<R>, InferLazy<R>> :
65 65 never :
66 66 never;
67 67
68 68 /**
69 69 * Интерфейс для конфигурирования сервиса в контейнере. Конфигурирование сервиса
70 70 * состоит из настройки различных параметров вызовами методов {@linkcode wants},
71 71 * {@linkcode lifetime}, {@linkcode override}, {@linkcode cleanup}. Завершение настройки
72 72 * сервиса осуществляется вызовом одного из методов {@linkcode factory} либо
73 73 * {@linkcode value}.
74 74 *
75 75 * @template S Карта сервисов контейнера, доступных при описании дескриптора
76 76 * @template T Тип сервиса
77 77 * @template R Карта зависимостей, которая передается параметром фабрике
78 78 * @template U Имена пользовательских сервисов, доступных для переопределения
79 79 */
80 80 export interface IDescriptorBuilder<S, T, R, U extends keyof S> {
81 81
82 82 /** Указывает фабрика для создания экземпляра сервиса, фабрика передается
83 83 * в виде параметра. При вызове фабрике будет передан объект с зависимостями,
84 84 * которые были предварительно указаны вызовами метода `wants(...)`
85 85 *
86 86 * Вызов данного метода завершает конфигурирование сервиса.
87 87 *
88 88 * @param f Фабрика для создания экземпляра сервиса.
89 89 */
90 90 factory(f: (refs: R) => NonNullable<T>): void;
91 91
92 92 /**
93 93 * Используется для указания зависимостей, которые потребуются фабричному
94 94 * методу при создании нового экземпляра сервиса. Данный метод может быть
95 95 * вызван несколько раз подряд, при этом вызовы этого метода имеют
96 96 * кумулятивный эффект.
97 97 *
98 98 * @template X Тип объекта с зависимостями, которые требуется получить при
99 99 * создании экземпляра фабрики при помощи фабричного метода.
100 100 * @param refs Объект с описанием зависимостей
101 101 * @returns Возвращает дескриптор сервиса, в котором указаны необходимые
102 102 * зависимости
103 103 */
104 104 wants<X extends DepsMap<S> & Record<keyof R & keyof X, never>>(refs: X):
105 105 IDescriptorBuilder<S, T, R & {
106 106 [k in keyof X]: Resolve<S, X[k]>;
107 107 }, U>
108 108
109 109 override<K extends U>(name: K, builder: BuildDescriptorFn<S, S[K], U>): this;
110 110 override<X extends ConfigurationMapConstraint<S, U, keyof X>>(services: X): this;
111 111
112 112 lifetime(lifetime: "singleton", typeId: string | number | object): this;
113 113 lifetime(lifetime: ILifetime<T> | Exclude<ActivationType, "singleton">): this;
114 114
115 115 /** Указывает функцию для освобождения экземпляра сервиса для случаев, когда
116 116 * время жизни привязано к контейнеру.
117 117 */
118 118 cleanup(cb: (item: T) => void): this;
119 119
120 120 /**
121 121 * Регистрирует в контейнере постоянное значение в качестве реализации сервиса.
122 122 *
123 123 * @param v Экземпляр реализации сервиса.
124 124 */
125 125 value(v: NonNullable<T>): void;
126 126 }
127 127
128 128 export type BuildDescriptorFn<S, T, U extends keyof S> = (d: IDescriptorBuilder<S, T, Record<never, never>, U>) => void;
129 129
130 130 /**
131 131 * Конфигурация контейнера, состоит из набора функций, которые выполняют конфигурацию.
132 132 *
133 133 * Все параметры конфигурации являются обязательными, если требуется ввести
134 134 * необязательные параметры, то нужно ограничить параметр типа {@linkcode K}
135 135 *
136 136 * @template S Сервисы доступные в контейнере
137 137 * @template K Сервисы участвующие в конфигурации
138 138 */
139 139 export type ConfigurationMap<S, K extends keyof S, U extends keyof S> = {
140 140 [k in K]-?: BuildDescriptorFn<S, S[k], U>
141 141 };
142 142
143 143 export type ConfigurationMapConstraint<S, U extends keyof S, X extends string | number | symbol> = {
144 144 [k in X]-?: k extends U ? BuildDescriptorFn<S, S[k], U> : never;
145 145 };
146 146
147 147 /**
148 148 * The type constraint useful to restrict type parameters to prevent defining
149 149 * the services with the {@link ContainerKeys} names.
150 150 *
151 151 * The constraint doesn't exclude using this keys but declares them as `never`
152 152 * which effectively will lead using this keys to the error.
153 153 */
154 154 export type ContainerServicesConstraint<S> = {
155 155 [k in keyof S]: k extends ContainerKeys ? never : S[k];
156 156 };
157 157
158 158 export interface Descriptor<S, T> {
159 159
160 160 /** This flags indicates that this registration can be replaced or overridden. */
161 161 readonly configurable?: boolean;
162 162
163 163 /** If specified signals the activation context that a new service scope
164 164 * should be created to isolate service overrides.
165 165 */
166 166 readonly hasOverrides?: boolean;
167 167
168 168 activate(context: IActivationContext<S>): NonNullable<T>;
169 169 }
170 170
171 171 /** The context used to initialize lifetime instance {@linkcode ILifetime} */
172 172 export interface ILifetimeContext {
173 contextSlot<T>(slotId: string): ILifetimeSlot<T>;
173 contextSlot<T>(slotId: string | number): ILifetimeSlot<T>;
174 174
175 containerSlot<T>(slotId: string): ILifetimeSlot<T>;
175 containerSlot<T>(slotId: string | number): ILifetimeSlot<T>;
176 176
177 177 }
178 178
179 179 export interface IActivationContext<S> extends ILifetimeContext, ServiceLocator<S> {
180 180
181 181 register<K extends keyof S>(name: K, service: DescriptorMap<S>[K]): void;
182 182
183 183 fail(error: unknown): never;
184 184 }
185 185
186 186 /**
187 187 * Descriptors map for the specified services {@linkcode S}. All entries are
188 188 * optional regardless the required or optional services in the original map.
189 189 *
190 190 * @template S Сервисы контекста активации
191 191 * @template U Карта сервисов которые создаются дескрипторами
192 192 */
193 193 export type DescriptorMap<S> = {
194 194 [k in keyof S]?: Descriptor<S, S[k]>;
195 195 };
196 196
197 197 type ContainerKeys = keyof ContainerProvided<object>;
198 198
199 199 export type ContainerProvided<S extends ContainerServicesConstraint<S>> = {
200 200 container: ServiceLocator<ContainerServices<S>>;
201 201
202 202 childContainer: IContainerBuilder<ContainerServices<S>, Exclude<keyof S, ContainerKeys>>;
203 203 };
204 204
205 205
206 206 /**
207 207 * Таблица сервисов, которые предоставляет контейнер.
208 208 *
209 209 * Сервисы, предоставляемые контейнером не могут быть null или undefined.
210 210 */
211 211 export type ContainerServices<S extends ContainerServicesConstraint<S>> = {
212 212 [k in keyof S | ContainerKeys]:
213 k extends ContainerKeys ? ContainerProvided<S>[k] :
214 k extends keyof S ? S[k] : never
213 k extends ContainerKeys ? ContainerProvided<S>[k] :
214 k extends keyof S ? S[k] : never
215 215 };
216 216
217 217
218 218 /**
219 219 * Returns the service declared in the type map {@link S}.
220 220 *
221 221 *
222 222 */
223 223 export interface ServiceLocator<S> {
224 224 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
225 225 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
226 226 }
227 227
228 228
229 229 export interface IContainerBuilder<S, U extends keyof S> {
230 230 createServiceBuilder<K extends U>(name: K):
231 231 IDescriptorBuilder<S, S[K], Record<never, never>, U>;
232 232
233 233 build(): ServiceLocator<S>;
234 234 }
235 235
236 236
237 237 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
238 238
239 239 /**
240 240 * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет
241 241 * свой собственный объект `ILifetime`, который создается при первой активации
242 242 */
243 export interface ILifetime<T> {
244 /** Проверяет, что уже создан экземпляр объекта */
245 has(): boolean;
243 export type ILifetime<T> = (context: ILifetimeContext) => ILifetimeSlot<NonNullable<T>>;
246 244
247 get(): NonNullable<T>;
245 export interface ILifetimeSlot<T> {
246 readonly has: () => boolean;
247
248 readonly get: () => T;
248 249
249 initialize(context: ILifetimeContext): void;
250 readonly initialize: () => void;
251
252 readonly store: (item: T, cleanup?: (item: T) => void) => void;
250 253
251 store(item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void): void;
254 readonly remove: () => void;
252 255
253 toString(): string;
256 readonly cleanup: () => void;
254 257 }
255 258
256 export interface ILifetimeSlot<T> {
257 has: () => boolean;
258
259 get: () => T;
260
261 initialize: () => void;
262
263 store(item: T, cleanup?: (item: T) => void): void;
264 }
265
266 259 export interface ILifetimeManager extends IDestroyable {
267 create<T>(): ILifetime<T>;
260 slot<T>(id: string | number): ILifetimeSlot<T>;
268 261 }
269 262
270 263 export type ExtractRequired<T, K extends keyof T = keyof T> = { [p in K as (undefined extends T[p] ? never : p)]-?: T[p] };
271 264
272 265 export type ExtractRequiredKeys<T, K extends keyof T = keyof T> = { [p in K]-?: undefined extends T[p] ? never : p }[K]; No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now