##// END OF EJS Templates
WIP lifetime services, change target to ES2018
cin -
r11:dd37d4287c45 default
parent child
Show More
@@ -1,144 +1,145
1 import { ActivationError } from "./ActivationError";
1 2 import { Descriptor, ILifetime, IActivationContext, DescriptorMap, ILifetimeManager } from "./interfaces";
2 import { argumentNotNull } from "./traits";
3 import { argumentNotNull, prototype } from "./traits";
3 4
4 5 export interface ActivationContextInfo {
5 6 name: string;
6 7
7 8 service: string;
8 9
9 10 }
10 11
11 12 let nextId = 1;
12 13
13 14 /** This object is created once per `Container.resolve` method call and used to
14 15 * cache dependencies and to track created instances. The activation context
15 16 * tracks services with `context` activation type.
16 17 *
17 18 * @template S The service map used in the activation context, services from
18 19 * this map are available to resolution.
19 20 * @template U A set of keys from the service map which can be overridden in
20 21 * this activation context.
21 22 */
22 23 export class ActivationContext<S> implements IActivationContext<S> {
23 24 private readonly _cache: Record<string, unknown>;
24 25
25 26 private readonly _services: DescriptorMap<S>;
26 27
27 28 private readonly _name: string;
28 29
29 30 private readonly _service: Descriptor<S, unknown>;
30 31
31 32 private readonly _containerLifetimeManager: ILifetimeManager;
32 33
33 34 private readonly _parent: ActivationContext<S> | undefined;
34 35
35 36 /** Creates a new activation context with the specified parameters.
36 37 * @param containerLifetimeManager the container which starts the activation process
37 38 * @param services the initial service registrations
38 39 * @param name the name of the service being activated, this parameter is
39 40 * used for the debug purpose.
40 41 * @param service the service to activate, this parameter is used for the
41 42 * debug purpose.
42 43 */
43 44 constructor(containerLifetimeManager: ILifetimeManager, services: DescriptorMap<S>, name: string, service: Descriptor<S, unknown>, cache = {}) {
44 45 this._name = name;
45 46 this._service = service;
46 47 this._cache = cache;
47 48 this._services = services;
48 49 this._containerLifetimeManager = containerLifetimeManager;
49 50 }
50 51
51 52 /** the name of the current resolving dependency */
52 53 getName() {
53 54 return this._name;
54 55 }
55 56
56 57 createContainerLifetime<T>() {
57 58 return this._containerLifetimeManager.create<T>();
58 59 }
59 60
60 61 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
61 62 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
62 63 resolve<K extends keyof S, T>(name: K, def?: T): NonNullable<S[K]> | T | undefined {
63 64 const d = this._services[name];
64 65
65 66 if (d !== undefined) {
66 67 return this.activate(d, name.toString());
67 68 } else {
68 69 if (arguments.length > 1)
69 70 return def;
70 71 else
71 72 throw new Error(`Service ${String(name)} not found`);
72 73 }
73 74 }
74 75
75 76 /**
76 77 * registers services local to the the activation context
77 78 *
78 79 * @name{string} the name of the service
79 80 * @service{string} the service descriptor to register
80 81 */
81 82 register<K extends keyof S>(name: K, service: DescriptorMap<S>[K]) {
82 83 argumentNotNull(name, "name");
83 84
84 85 const d = this._services[name];
85 86 if (d !== undefined && !d.configurable)
86 87 throw new Error(`Service ${String(name)} can't be overridden`);
87 88
88 89 this._services[name] = service;
89 90 }
90 91
91 92 createLifetime<T>(): ILifetime<T> {
92 93 const id = nextId++;
93 94 return {
94 95 initialize() { },
95 96 has: () => id in this._cache,
96 97 get: () => {
97 98 const v = this._cache[id] as T;
98 99 if (v === undefined || v === null)
99 100 throw new Error("The value isn't present in the activation context");
100 101 return v;
101 102 },
102 103 store: item => {
103 104 this._cache[id] = item;
104 105 }
105 106 };
106 107 }
107 108
108 109 activate<T>(d: Descriptor<S, T>, name: string) {
109 110 // TODO: add logging
110 111 // if (trace.isLogEnabled())
111 112 // trace.log("enter {0} {1}", name, d);
112 113
113 const ctx = this.enter(d, name);
114 const ctx = new ActivationContext(
115 this._containerLifetimeManager,
116 d.hasOverrides ? prototype(this._services) : this._services,
117 name,
118 d,
119 this._cache
120 );
121
114 122 const v = d.activate(ctx);
115 123
116 124 // if (trace.isLogEnabled())
117 125 // trace.log(`leave ${name}`);
118 126
119 127 return v;
120 128 }
121 129
122 130 getStack(): ActivationContextInfo[] {
123 131 const stack = [{
124 132 name: this._name,
125 133 service: this._service.toString()
126 134 }];
127 135
128 136 return this._parent ?
129 137 stack.concat(this._parent.getStack()) :
130 138 stack;
131 139 }
132 140
133 private enter(service: Descriptor<S, unknown>, name: string) {
134 return new ActivationContext(
135 this._containerLifetimeManager,
136 service.hasOverrides ?
137 Object.create(this._services) as typeof this._services :
138 this._services,
139 name,
140 service,
141 this._cache
142 );
141 fail(innerException: unknown): never {
142 throw new ActivationError(this._name, this.getStack(), innerException);
143 143 }
144
144 145 }
@@ -1,67 +1,67
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 import { isDestroyable } from "./traits";
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 constructor(parentServices?: DescriptorMap<S>, lifetime?: ILifetime<IDestroyable>) {
22 this._services = Object.create(parentServices ? parentServices : null) as DescriptorMap<S>;
21 constructor(parentServices: DescriptorMap<S> | null = null, lifetime?: ILifetime<IDestroyable>) {
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 38 const lifetime = this._lifetime;
39 39
40 40 const detach = isDestroyable(lifetime) ? () => lifetime.destroy() : () => void (0);
41 41
42 42 const container = new Container(this._services, this._lifetimeManager, detach);
43 43 lifetime.store(container);
44 44
45 45 return container;
46 46 }
47 47
48 48 private readonly _register = <K extends U>(name: K) =>
49 49 (descriptor: Descriptor<S, S[K]>) => {
50 50 this._complete();
51 51 this._services[name] = descriptor;
52 52 };
53 53
54 54 private readonly _fail = (ex: unknown) => {
55 55 throw ex;
56 56 };
57 57
58 58 private _assertBuilding() {
59 59 if (!this._pending)
60 60 throw new Error("The descriptor builder is finalized");
61 61 }
62 62
63 63 private _complete() {
64 64 return !(--this._pending);
65 65 }
66 66
67 67 } No newline at end of file
@@ -1,84 +1,93
1 import { ActivationError } from "./ActivationError";
1 2 import { Descriptor, ILifetime, DepsMap, IActivationContext, DescriptorMap } from "./interfaces";
2 3 import { each, key } from "./traits";
3 4
4 5 export interface DescriptorImplArgs<S, T> {
5 6 lifetime: ILifetime<T>;
6 7
7 8 factory: (refs: Record<key, unknown>) => NonNullable<T>;
8 9
9 10 cleanup?: (item: NonNullable<T>) => void;
10 11
11 12 overrides?: DescriptorMap<S>;
12 13
13 14 dependencies?: DepsMap<S>;
14 15 }
15 16
16 17
17 18 export class DescriptorImpl<S, T> implements Descriptor<S, T> {
18 19
19 20 private readonly _overrides?: DescriptorMap<S>;
20 21
21 22 private readonly _lifetime: ILifetime<T>;
22 23
23 24 private readonly _factory: (refs: Record<key, unknown>) => NonNullable<T>;
24 25
25 26 private readonly _cleanup?: (item: NonNullable<T>) => void;
26 27
27 28 private readonly _deps?: DepsMap<S>;
28 29
29 30 readonly hasOverrides: boolean;
30 31
31 32 constructor({ lifetime, factory, cleanup, overrides, dependencies }: DescriptorImplArgs<S, T>) {
32 33 this._lifetime = lifetime;
33 34 this._factory = factory;
34 35 if (cleanup)
35 36 this._cleanup = cleanup;
36 37 if (overrides)
37 38 this._overrides = overrides;
38 39 if (dependencies)
39 40 this._deps = dependencies;
40 41
41 42 this.hasOverrides = !!overrides;
42 43 }
43 44
44 45 activate(context: IActivationContext<S>): NonNullable<T> {
45 46
46 47 if (this._lifetime.has())
47 48 return this._lifetime.get();
48 49
49 50 this._lifetime.initialize(context);
50 51
51 52 if (this._overrides)
52 53 each(this._overrides, (v, k) => context.register(k, v));
53 54
54 55 const resolve = <K extends keyof S>({ name, lazy, ...opts }: { name: K; lazy?: boolean; default?: S[K] | null; }) => {
55 56 if (lazy) {
56 return () => "default" in opts ? context.resolve(name, opts.default) : context.resolve(name);
57 return "default" in opts ?
58 () => context.resolve(name, opts.default) :
59 () => context.resolve(name);
57 60 } else {
58 return "default" in opts ? context.resolve(name, opts.default) : context.resolve(name);
61 return "default" in opts ?
62 context.resolve(name, opts.default) :
63 context.resolve(name);
59 64 }
60 65 };
61 66
62 const makeRefs = (deps: typeof this._deps) => deps ?
67 const deps = this._deps;
68
69 const refs = deps ?
63 70 Object.keys(deps)
64 71 .map(k => {
65 72 const ref = deps[k];
66 73 return typeof ref !== "object" ?
67 74 { [k]: resolve({ name: ref }) } :
68 75 { [k]: resolve(ref) };
69 76 })
70 77 .reduce((a, p) => ({ ...a, ...p }), {} ):
71 78 {};
72 79
73 const instance = this._factory.call(undefined, makeRefs(this._deps));
74
80 try {
81 const instance = (0,this._factory)(refs);
75 82 this._lifetime.store(instance, this._cleanup);
76
77 83 return instance;
84 } catch(err) {
85 context.fail(err);
86 }
78 87 }
79 88
80 89
81 90 toString() {
82 91 return `[object DescriptorImpl, lifetime=${String(this._lifetime)}]`;
83 92 }
84 93 }
@@ -1,243 +1,252
1 1 import { IActivationContext, ILifetime, ILifetimeContext, ILifetimeManager, ILifetimeSlot } from "./interfaces";
2 2 import { ActivationContext } from "./ActivationContext";
3 3 import { argumentNotNull, isDestroyable } from "./traits";
4 4
5 5 const safeCall = (item: () => void) => {
6 6 try {
7 7 item();
8 8 } catch {
9 9 // silence!
10 10 }
11 11 };
12 12
13 const noop = () => {};
14
15 const fail = (message: string) => {
16 throw new Error(message);
17 };
18
13 19 const _emptySlot = Object.freeze({
14 20 has: () => false,
15 21
16 initialize: () => void (0),
22 initialize: noop,
23
24 get: fail("The specified item isn't registered with a lifetime manager"),
17 25
18 get: () => {
19 throw new Error("The specified item isn't registered with this lifetime manager");
20 },
26 store: noop,
21 27
22 store: () => void (0)
28 remove: noop
23 29 });
24 30
25 31 const _unknonwSlot = Object.freeze({
26 32 has: () => false,
27 33
28 34 initialize: () => {
29 35 throw new Error("Can't call initialize on the unknown lifetime object");
30 36 },
31 37 get: () => {
32 38 throw new Error("The lifetime object isn't initialized");
33 39 },
34 40 store: () => {
35 41 throw new Error("Can't store a value in the unknown lifetime object");
36 42 }
37 43 });
38 44
39 let nextId = 0;
40
41 const singletons: { [K: string]: unknown } = {};
42
43 45
44 46 const pendingSlot = <T>(store: (item: T) => void) => ({
45 47 has: () => false,
46 48
47 49 get: () => {
48 50 throw new Error("The value in this slot doesn't exist");
49 51 },
50 52
51 53 initialize: () => {
52 54 throw new Error("Cyclic reference detected");
53 55 },
54 56
55 57 store
56 58 });
57 59
58 const noop = () => void(0);
59
60 60 const valueSlot = <T>(value: T, cleanup: (item: T) => void) => ({
61 61 has: () => true,
62 62
63 63 get: () => value,
64 64
65 65 initialize: () => {
66 66 throw new Error("The slot already has a value");
67 67 },
68 68
69 69 store: () => {
70 70 throw new Error("The slot already has a value");
71 71 }
72 72 });
73 73
74 const singletons: { [K: string]: unknown } = {};
75
74 76 export class LifetimeManager implements ILifetimeManager {
75 77 private _destroyed = false;
76 78
77 79 private readonly _slots: Record<string, ILifetimeSlot<unknown>> = {};
78 80
79 81 slot<T>(cookie: string): ILifetimeSlot<T> {
80 82 if (cookie in this._slots)
81 83 return this._slots[cookie] as ILifetimeSlot<T>;
82 84
83 85 const store = (item: T, cleanup?: (item: T) => void) => {
84 86 this._assertNotDestroyed();
85 87 this._slots[cookie] = valueSlot(
86 88 item,
87 89 cleanup ?? isDestroyable(item) ? () => item.destroy() : noop
88 90 );
89 91 };
90 92
91 93 return {
92 94 has: () => false,
93 95 get: () => {
94 96 throw new Error("The value isn't stored in this slot");
95 97 },
96 98 store,
97 99 initialize: () => {
98 100 this._assertNotDestroyed();
99 101 this._slots[cookie] = pendingSlot(store);
100 102 }
101 103 };
102 104 }
103 105
106 remove(cookie: string) {
107 delete this._slots[cookie];
108 }
109
104 110 private _assertNotDestroyed() {
105 111 if (this._destroyed)
106 112 throw new Error("The lifetime manager is destroyed");
107 113
108 114 }
109 115
110 116 destroy() {
111 117 if (!this._destroyed) {
112 118 this._destroyed = true;
113 this._cleanup.forEach(safeCall);
114 this._cleanup.length = 0;
119 Object.values(this._slots).forEach(({clean}) => )
115 120 }
116 121 }
117 122
118 123 }
119 124
120 125 export const emptyLifetime = <T>(): ILifetime<T> => {
121 126 return _emptyLifetime;
122 127 };
123 128
124 129 export const hierarchyLifetime = <T>(): ILifetime<T> => {
130 // TODO: вот здесь ошибка, при первой активации сервиса будет получен и
131 // привязан lifetime из дочернего контейнера, при активации через второй
132 // дочерний контейнера это приведет к ошибке, точнее будет взят экземпляр
133 // из первого контейнера.
125 134 let _lifetime: ILifetime<T> = _unknownLifetime;
126 135 return {
127 136 initialize(context: ILifetimeContext) {
128 137 if (_lifetime !== _unknownLifetime)
129 138 throw new Error("Cyclic reference activation detected");
130 139
131 140 _lifetime = context.createContainerLifetime<T>();
132 141 },
133 142 get() {
134 143 return _lifetime.get();
135 144 },
136 145 has() {
137 146 return _lifetime.has();
138 147 },
139 148 store(item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void) {
140 149 return _lifetime.store(item, cleanup);
141 150 },
142 151 toString() {
143 152 return `[object HierarchyLifetime, has=${String(this.has())}]`;
144 153 }
145 154 };
146 155 };
147 156
148 157 /**
149 158 * Creates a lifetime instance bound to the current activation context. This
150 159 * lifetime will store the service instance per activation context. Every
151 160 * top level service resolution will create a new activation context. This
152 161 * context is propagated to subsequent service resolution thus all services
153 162 * with context lifetime will be shared among their consumers.
154 163 *
155 164 * @returns The instance of the lifetime.
156 165 */
157 166 export const contextLifetime = <T>(): ILifetime<T> => {
158 167 let _lifetime: ILifetime<T> = _unknownLifetime;
159 168 return {
160 169 initialize(context: ILifetimeContext) {
161 170 if (_lifetime !== _unknownLifetime)
162 171 throw new Error("Cyclic reference detected");
163 172 _lifetime = context.createLifetime();
164 173 },
165 174 get() {
166 175 return _lifetime.get();
167 176 },
168 177 has() {
169 178 return _lifetime.has();
170 179 },
171 180 store(item: NonNullable<T>) {
172 181 _lifetime.store(item);
173 182 },
174 183 toString() {
175 184 return `[object ContextLifetime, has=${String(this.has())}]`;
176 185 }
177 186 };
178 187 };
179 188
180 189 /**
181 190 * Creates the lifetime for the service which will allow existence only one
182 191 * instance with the specified {@linkcode typeId}. If there will be created
183 192 * several lifetime instances with same `typeId` in the runtime, they will
184 193 * share the same service instance.
185 194 *
186 195 * @param typeId The identified for the global instance, usually this is a
187 196 * fully qualified class name
188 197 * @returns The lifetime instance
189 198 */
190 199 export const singletonLifetime = <T>(typeId: string): ILifetime<T> => {
191 200 argumentNotNull(typeId, "typeId");
192 201 let pending = false;
193 202 return {
194 203 has() {
195 204 return typeId in singletons;
196 205 },
197 206 get() {
198 207 if (!this.has())
199 208 throw new Error(`The instance ${typeId} doesn't exists`);
200 209 return singletons[typeId] as NonNullable<T>;
201 210 },
202 211 initialize() {
203 212 if (pending)
204 213 throw new Error("Cyclic reference detected");
205 214 pending = true;
206 215 },
207 216 store(item: NonNullable<T>) {
208 217 singletons[typeId] = item;
209 218 pending = false;
210 219 },
211 220 toString() {
212 221 return `[object SingletonLifetime, has=${String(this.has())}, typeId=${typeId}]`;
213 222 }
214 223 };
215 224 };
216 225
217 226 /** Creates a lifetime bound to the specified container. Using this lifetime
218 227 * will create a single service instance per the specified container.
219 228 *
220 229 * @param container The container which will manage the lifetime for the service
221 230 */
222 231 export const containerLifetime = <T>(container: { createLifetime<X>(): ILifetime<X> }) => {
223 232 let _lifetime: ILifetime<T> = _unknownLifetime;
224 233 return {
225 234 initialize() {
226 235 if (_lifetime !== _unknownLifetime)
227 236 throw new Error("Cyclic reference detected");
228 237 _lifetime = container.createLifetime();
229 238 },
230 239 get() {
231 240 return _lifetime.get();
232 241 },
233 242 has() {
234 243 return _lifetime.has();
235 244 },
236 245 store(item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void) {
237 246 _lifetime.store(item, cleanup);
238 247 },
239 248 toString() {
240 249 return `[object ContainerLifetime, has=${String(_lifetime.has())}]`;
241 250 }
242 251 };
243 252 };
@@ -1,273 +1,272
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 createLifetime<T>(): ILifetime<T>;
173 contextSlot<T>(slotId: string): ILifetimeSlot<T>;
174 174
175 createContainerLifetime<T>(): ILifetime<T>;
175 containerSlot<T>(slotId: string): 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
183 fail(error: unknown): never;
182 184 }
183 185
184 186 /**
185 187 * Descriptors map for the specified services {@linkcode S}. All entries are
186 188 * optional regardless the required or optional services in the original map.
187 189 *
188 190 * @template S Сервисы контекста активации
189 191 * @template U Карта сервисов которые создаются дескрипторами
190 192 */
191 193 export type DescriptorMap<S> = {
192 194 [k in keyof S]?: Descriptor<S, S[k]>;
193 195 };
194 196
195 197 type ContainerKeys = keyof ContainerProvided<object>;
196 198
197 199 export type ContainerProvided<S extends ContainerServicesConstraint<S>> = {
198 200 container: ServiceLocator<ContainerServices<S>>;
199 201
200 202 childContainer: IContainerBuilder<ContainerServices<S>, Exclude<keyof S, ContainerKeys>>;
201 203 };
202 204
203 205
204 206 /**
205 207 * Таблица сервисов, которые предоставляет контейнер.
206 208 *
207 209 * Сервисы, предоставляемые контейнером не могут быть null или undefined.
208 210 */
209 211 export type ContainerServices<S extends ContainerServicesConstraint<S>> = {
210 212 [k in keyof S | ContainerKeys]:
211 213 k extends ContainerKeys ? ContainerProvided<S>[k] :
212 214 k extends keyof S ? S[k] : never
213 215 };
214 216
215 217
216 218 /**
217 219 * Returns the service declared in the type map {@link S}.
218 220 *
219 221 *
220 222 */
221 223 export interface ServiceLocator<S> {
222 224 resolve<K extends keyof S>(name: K): NonNullable<S[K]>;
223 225 resolve<K extends keyof S, T>(name: K, def: T): NonNullable<S[K]> | T;
224 226 }
225 227
226 export interface LifetimeContainer {
227 createLifetime<T>(): ILifetime<T>;
228 }
229 228
230 229 export interface IContainerBuilder<S, U extends keyof S> {
231 230 createServiceBuilder<K extends U>(name: K):
232 231 IDescriptorBuilder<S, S[K], Record<never, never>, U>;
233 232
234 233 build(): ServiceLocator<S>;
235 234 }
236 235
237 236
238 237 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
239 238
240 239 /**
241 240 * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет
242 241 * свой собственный объект `ILifetime`, который создается при первой активации
243 242 */
244 243 export interface ILifetime<T> {
245 244 /** Проверяет, что уже создан экземпляр объекта */
246 245 has(): boolean;
247 246
248 247 get(): NonNullable<T>;
249 248
250 249 initialize(context: ILifetimeContext): void;
251 250
252 251 store(item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void): void;
253 252
254 253 toString(): string;
255 254 }
256 255
257 256 export interface ILifetimeSlot<T> {
258 257 has: () => boolean;
259 258
260 259 get: () => T;
261 260
262 261 initialize: () => void;
263 262
264 263 store(item: T, cleanup?: (item: T) => void): void;
265 264 }
266 265
267 266 export interface ILifetimeManager extends IDestroyable {
268 267 create<T>(): ILifetime<T>;
269 268 }
270 269
271 270 export type ExtractRequired<T, K extends keyof T = keyof T> = { [p in K as (undefined extends T[p] ? never : p)]-?: T[p] };
272 271
273 272 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
@@ -1,49 +1,52
1 1 import { FluentConfiguration } from "./FluentConfiguration";
2 2 import { ContainerServices, ContainerServicesConstraint, IDestroyable } from "./interfaces";
3 3
4 4 export function fluent<S extends ContainerServicesConstraint<S>>() {
5 5 return new FluentConfiguration<S>();
6 6 }
7 7
8 8 export type key = string | number | symbol;
9 9
10 10 export const isKey = (v: unknown): v is key =>
11 11 typeof v === "string" || typeof v === "number" || typeof v === "symbol";
12 12
13 13 export const isString = (v: unknown): v is string =>
14 14 typeof v === "string";
15 15
16 16
17 17 export const isNotNull = <T>(v: T): v is NonNullable<T> => v !== null && v !== undefined;
18 18
19 19 export const each = <T extends object>(obj: T, cb: <X extends Extract<keyof T, string>>(v: NonNullable<T[X]>, k: X) => void) =>
20 20 (Object.keys(obj) as (Extract<keyof T, string>)[])
21 21 .forEach(k => {
22 22 const v = obj[k];
23 23 isNotNull(v) && cb(v, k);
24 24 });
25 25
26 26 export const argumentNotNull = (arg: unknown, name: string) => {
27 27 if (arg === null || arg === undefined)
28 28 throw new Error("The argument " + name + " can't be null or undefined");
29 29 };
30 30
31 31 export const isPromise = <T = unknown>(val: unknown): val is PromiseLike<T> =>
32 32 isNotNull(val) && typeof (val as PromiseLike<T>).then === "function";
33 33
34 34 export const isDestroyable = (d: unknown): d is IDestroyable =>
35 35 isNotNull(d) && typeof (d as IDestroyable).destroy === "function";
36 36
37 37 let _nextOid = 0;
38 38 const _oid = typeof Symbol === "function" ?
39 39 Symbol.for("__implab__oid__") :
40 40 "__implab__oid__";
41 41
42 42 type OidSlot = { [k in typeof _oid]?: string };
43 43
44 44 export const oid = <T extends object>(instance: T): string => {
45 45 argumentNotNull(instance, "instance");
46 46 const val = (instance as OidSlot)[_oid];
47 47
48 48 return val ? val : ((instance as OidSlot)[_oid] = `oid_${++_nextOid}`);
49 }; No newline at end of file
49 };
50
51 export const prototype = <T extends object>(instance: T | null): T =>
52 Object.create(instance) as T; No newline at end of file
@@ -1,12 +1,12
1 1 {
2 2 "compilerOptions": {
3 3 "moduleResolution": "node",
4 4 "experimentalDecorators": true,
5 5 "noEmitOnError": true,
6 6 "listFiles": true,
7 7 "strict": true,
8 8 "types": [],
9 "target": "ES5",
10 "lib": ["es5", "es2015.promise", "es2015.symbol", "es2015.iterable", "dom", "scripthost"]
9 "target": "ES2018",
10 "lib": ["ES2018", "DOM", "ScriptHost"]
11 11 }
12 12 } No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now