##// END OF EJS Templates
working on fluent configuration
cin -
r133:09ea4b9e3735 ioc ts support
parent child
Show More
@@ -0,0 +1,59
1 import { Container } from "../Container";
2 import { argumentNotNull, each } from "../../safe";
3 import { DescriptorBuilder } from "./DescriptorBuilder";
4
5 enum State {
6 ready,
7
8 pending
9 }
10
11 export class Configuration<S extends object, Y extends keyof S = keyof S> {
12
13 private _state = State.ready;
14
15 _completion: PromiseLike<void> = Promise.resolve();
16
17 _builders: { [k in keyof S]?: (service: DescriptorBuilder<S[k], S>) => void } = {};
18
19 register<K extends Y>(name: K, builder: (service: DescriptorBuilder<S[K], S>) => void): Configuration<S, Exclude<Y, K>> {
20 argumentNotNull(builder, "builder");
21
22 return this;
23 }
24
25 private _moveStart() {
26 if (this._state !== State.ready)
27 throw new Error("Invalid operation");
28
29 this._state = State.pending;
30 }
31
32 private _moveDone() {
33 this._state = State.ready;
34 }
35
36 apply(target: Container<S>) {
37 this._moveStart();
38
39 let pending = 1;
40
41 this._completion = new Promise((resolve, reject) => {
42 each(this._builders, (v, k) => {
43 pending++;
44 const d = new DescriptorBuilder<any, S>(target, result => {
45 target.register(k, result);
46 if (!--pending)
47 resolve();
48 });
49
50 try {
51 v(d);
52 } catch (e) {
53 reject(e);
54 }
55 });
56 }).then(() => this._moveDone());
57 }
58
59 }
@@ -0,0 +1,58
1 import { Resolver, ServiceModule, LazyDependencyOptions, DependencyOptions } from "./interfaces";
2 import { AnnotationBuilder } from "../Annotations";
3 import { Container } from "../Container";
4 import { Descriptor, ILifetime, ContainerKeys } from "../interfaces";
5 import { ActivationContext } from "../ActivationContext";
6
7 export class DescriptorBuilder<T, S extends object> {
8 readonly _container: Container<S>;
9 readonly _cb: (d: Descriptor<S, T>) => void;
10
11 constructor(container: Container<S>, cb: (d: Descriptor<S, T>) => void) {
12 this._container = container;
13 this._cb = cb;
14 }
15 service(service: AnnotationBuilder<T, S> | ServiceModule<T, S>) {
16
17 }
18
19 factory(f: (resolve: Resolver<S>, activate: (lifetime: ILifetime, factory: () => any, cleanup?: (item: any) => void) => any) => T): void {
20 this._cb({
21 activate(context: ActivationContext<S>) {
22 const resolve = (name: ContainerKeys<S>, opts?: DependencyOptions | LazyDependencyOptions) => {
23 if (opts && "lazy" in opts && opts.lazy) {
24 const c2 = context.clone();
25 return () => {
26 return opts.optional ? c2.resolve(name, opts.default) : c2.resolve(name);
27 };
28 } else {
29 return opts && opts.optional ? context.resolve(name, opts.default) : context.resolve(name);
30 }
31 };
32
33 const activate = (lifetime: ILifetime, factory: () => any, cleanup?: (item: any) => void) => {
34 if (lifetime.has()) {
35 return lifetime.get();
36 } else {
37 lifetime.enter();
38 const instance = factory();
39 lifetime.store(instance, cleanup);
40 return instance;
41 }
42
43 };
44
45 return f(resolve, activate);
46 }
47 });
48 }
49
50 value(v: T): void {
51 this._cb({
52 activate() {
53 return v;
54 }
55 });
56 }
57
58 }
@@ -1,182 +1,182
1 1 # Observable
2 2
3 3 Универсальный способ организации потока сообщений. Данный механизм может
4 4 использоваться для оповещения об изменениях состояний объектов или для доставки
5 5 самостоятельных событий, например, связанных с действиями пользователя.
6 6
7 7 Является реализацией классического шаблона наблюдателя с возможность сообщить
8 о коце потока событий. Данная реализация не содержит никаких дополнительных
8 о конце потока событий. Данная реализация не содержит никаких дополнительных
9 9 функций, таких как фильтрация, канал с состоянием, преобразования сообщений и
10 10 т.п. Это сделано специально, чтобы реализация оставалась максимально простой.
11 11
12 12 Пример того, как можно создать последовательность из 10 событий:
13 13
14 14 ```ts
15 15 var events = new Observable(async (notify, error, complete) => {
16 16 // цикл в котором возникает событие
17 17 for(let i = 0; i < 10; i++) {
18 18 await delay(1000);
19 19 // в качестве данных передается номер события
20 20 notify(i);
21 21 }
22 22 // по окончании последовательности информируем, что событий больше не будет
23 compelte();
23 complete();
24 24 });
25 25
26 26 // создаем окно с отображением хода событий
27 27 var progress = showProgress({ min: 0, max: 9, current: 0});
28 28
29 29 // подписываемся на события
30 30 events.on(
31 31 // обработчик очередного события
32 32 msg => {
33 33 progress.setValue(msg);
34 34 }.
35 35 // обработчик ошибки
36 36 e => {
37 37 progress.showError(e);
38 38 },
39 39 // обработчик конца потока
40 40 () => {
41 41 progress.close();
42 42 }
43 43 );
44 44
45 45 // ожидание следующего события
46 46 let firstEvent = await events.next();
47 47 ```
48 48
49 49 `Observable` можно создавать из событий другого объекта, например, виджета:
50 50
51 51 ```ts
52 // клсс
52 // класс
53 53 class Canvas {
54 readonly mouseMove: IObservable<[number,number]>
54 mouseMove: IObservable<[number,number]>;
55 55
56 56 postCreate() {
57 57 // превращаем события виджета в Observable
58 58 this.mouseMove = new Observable<[number,number]>((notify) => {
59 59 this.mousePad.on('mousemove',(e) => notify([e.clientX, e.clientY]) );
60 60 });
61 61 }
62 62 }
63 63
64 64 ```
65 65
66 66 Если объект инкапсулирует в себе `Observable`, он также может сохранить методы
67 67 для оповещения подписчиков для дальнейшего их использования внутри класса.
68 68
69 69 ```ts
70 70 // класс, который будет генерировать события местоположения
71 71 class PositionTracker implements IDestroyable {
72 72 // _nextPosition и _complete будут связаны с position при создании
73 73 // экземпляра PositionTracker.
74 74 _nextPosition: (pos: Position) => void
75 75 _complete: () => void
76 76
77 77 readonly position: IObservable<Position>
78 78
79 79 // конструктор
80 80 constructor(...args: any[]) {
81 81 super(args);
82 82
83 83 // создаем Observable
84 84 this.position = new Observable<Position>((notify, error, complete) => {
85 85 // сохраняем методы для оповещения о новом местоположении
86 86 this._nextPosition = notify;
87 87 // метод об оповещении конца потока событий
88 88 this._complete = complete
89 89 });
90 90 }
91 91
92 92 // метод для очистки ресурсов
93 93 destroy() {
94 94 this._complete();
95 95
96 96 super();
97 97 }
98 98 }
99 99 ```
100 100
101 Существует также несколько варинатов получения сообщений
101 Существует также несколько вариантов получения сообщений
102 102
103 103 ```ts
104 104 // регистрация метода для получений событий
105 105 let subscription = pushEvents.on((msg) => {
106 106 displayPopup(msg);
107 107 });
108 108
109 109 // подписку можно отменить, после чего обработчики больше не будут вызываться
110 110 subcription.destroy();
111 111
112 112 // если требуется получить только одно сообщение можно использовать
113 113 // асинхронный метод next(ct?: ICancellation)
114 114
115 115 let msg = await pushEvents.next();
116 116
117 117 // пример метода для получения координат с карты, который использует
118 118 // событие нажатия мышью для определения координат.
119 119
120 120 class Map {
121 121 /**
122 122 Получает координаты по щелчку мыши.
123 123 @async
124 124 @returns [lon,lat]
125 125 */
126 126 async peekCoordinates(ct: ICancellation = Cancellation.none) {
127 127 // получаем событие клика
128 128 let evt = this.viewport.click.next(ct);
129 129
130 130 // преобразуем позицию на экране в координаты карты
131 return this.clientToCoodinates([evt.clientx,evt.clientY]);
131 return this.clientToCoordinates([evt.clientX,evt.clientY]);
132 132 }
133 133 }
134 134
135 135
136 136 let map : Map; // где-то объявлено
137 137
138 138 // пример получения координат с карты
139 139 let coords = await map.peekCoordinates();
140 140
141 141 ```
142 142
143 143 ## Observable и последовательности
144 144
145 Можно сичтать, что `Observable` это некоторая аналогия итератора только в
146 парадигме событийного (или реактивного) программировния. Следует также понимать,
145 Можно считать, что `Observable` это некоторая аналогия итератора только в
146 парадигме событийного (или реактивного) программирования. Следует также понимать,
147 147 что при переходе от синхронного процедурного программирования к событийному так
148 148 же меняется и направление управления (Inverse Of Control), что означает
149 149 следующее:
150 150
151 151 * при работе с итераторами клиенты сами определяют момент чтения следующего
152 152 элемента последовательности.
153 153 * при работе с `Observable` клиенты вынуждены обрабатывать эти события по мере
154 154 их поступления и не могут на это повлиять.
155 155
156 Последний пункт можно изменить применив, например, буффер или канал с
156 Последний пункт можно изменить применив, например, буфер или канал с
157 157 состоянием, т.е. очередь, но данные механизмы выходят за рамки простого шаблона
158 158 наблюдателя.
159 159
160 160 ```ts
161 161 // обработка в цикле не гарантирует получения всех сообщений
162 162 while(1) {
163 163 // ожидаем следующее событие, по сути это подписка только на одно событие
164 164 let next = await events.next();
165 165
166 166 // такой цикл может пропускать сообщения, поскольку асинхронная операция
167 167 // позволит возобновить создание новых событий, на которые мы не подписаны
168 168 await processEvent(next);
169 169
170 170 // не только асинхронные операции могут привести к пропуску события
171 171 // например вызов метода, который приводит к созданию события так же
172 172 // приведет к тому, что созданное событие не будет обработано в текущем
173 173 // цикле
174 174 doSmthAndRiseEvent();
175 175 }
176 176
177 177 // для получения всех сообщений нужно регистрировать подписчика
178 178 events.on((data) => {
179 179 // будет вызван для всех сообщений
180 180 processEvent(data);
181 181 });
182 ``` No newline at end of file
182 ```
@@ -1,205 +1,199
1 1 import { IObservable, IDestroyable, ICancellation, IObserver } from "./interfaces";
2 2 import { Cancellation } from "./Cancellation";
3 import { argumentNotNull, destroyed } from "./safe";
3 import { argumentNotNull } from "./safe";
4 4
5 5 type Handler<T> = (x: T) => void;
6 6
7 7 type Initializer<T> = (notify: Handler<T>, error: (e: any) => void, complete: () => void) => void;
8 8
9 9 const noop = () => { };
10 10
11 const nulObserver: IObserver<any> = Object.freeze({
12 next: noop,
13 error: noop,
14 complete: noop
15 });
16
17 11 function isObserver(val: any): val is IObserver<any> {
18 12 return val && (typeof val.next === "function");
19 13 }
20 14
21 15 export class Observable<T> implements IObservable<T> {
22 16 private _once = new Array<IObserver<T>>();
23 17
24 18 private _observers = new Array<IObserver<T>>();
25 19
26 20 private _complete = false;
27 21
28 22 private _error: any;
29 23
30 24 constructor(func?: Initializer<T>) {
31 25 if (func)
32 26 func(
33 27 this._notifyNext.bind(this),
34 28 this._notifyError.bind(this),
35 29 this._notifyCompleted.bind(this)
36 30 );
37 31 }
38 32
39 33 /**
40 34 * Registers handlers for the current observable object.
41 35 *
42 36 * @param next the handler for events
43 37 * @param error the handler for a error
44 38 * @param complete the handler for a completion
45 39 * @returns {IDestroyable} the handler for the current subscription, this
46 40 * handler can be used to unsubscribe from events.
47 41 *
48 42 */
49 43 on(next: Handler<T>, error?: Handler<any>, complete?: () => void): IDestroyable {
50 44 argumentNotNull(next, "next");
51 45
52 46 const me = this;
53 47
54 48 const observer: IObserver<T> & IDestroyable = {
55 49 next: next.bind(null),
56 50 error: error ? error.bind(null) : noop,
57 51 complete: complete ? complete.bind(null) : noop,
58 52
59 53 destroy() {
60 54 me._removeObserver(this);
61 55 }
62 56 };
63 57
64 58 this._addObserver(observer);
65 59
66 60 return observer;
67 61 }
68 62
69 63 subscribe(next: IObserver<T> | Handler<T>, error?: Handler<any>, complete?: () => void): IDestroyable {
70 64 if (isObserver(next)) {
71 65 this._addObserver(next);
72 66 return {
73 67 destroy: () => this._removeObserver(next)
74 68 };
75 69 } else {
76 70 const observer = {
77 71 next: next.bind(null),
78 72 error: error ? error.bind(null) : noop,
79 73 complete: complete ? complete.bind(null) : noop
80 74 };
81 75
82 76 this._addObserver(observer);
83 77 return {
84 78 destroy: () => this._removeObserver(observer)
85 79 };
86 80 }
87 81 }
88 82
89 83 private _addObserver(observer: IObserver<T>) {
90 84 if (this._complete) {
91 85 try {
92 86 if (this._error)
93 87 observer.error(this._error);
94 88 else
95 89 observer.complete();
96 90 } catch (e) {
97 91 this.onObserverException(e);
98 92 }
99 93 } else {
100 94 this._observers.push(observer);
101 95 }
102 96 }
103 97
104 98 /**
105 99 * Waits for the next event. This method can't be used to read messages
106 100 * as a sequence since it can skip some messages between calls.
107 101 *
108 102 * @param ct a cancellation token
109 103 */
110 104 next(ct: ICancellation = Cancellation.none) {
111 105 return new Promise<T>((resolve, reject) => {
112 106 const observer: IObserver<T> = {
113 107 next: resolve,
114 108 error: reject,
115 109 complete: () => reject("No more events are available")
116 110 };
117 111
118 112 if (this._addOnce(observer) && ct.isSupported()) {
119 113 ct.register(e => {
120 114 this._removeOnce(observer);
121 115 reject(e);
122 116 });
123 117 }
124 118 });
125 119 }
126 120
127 121 private _addOnce(observer: IObserver<T>) {
128 122 if (this._complete) {
129 123 try {
130 124 if (this._error)
131 125 observer.error(this._error);
132 126 else
133 127 observer.complete();
134 128 } catch (e) {
135 129 this.onObserverException(e);
136 130 }
137 131 return false;
138 132 }
139 133
140 134 this._once.push(observer);
141 135 return true;
142 136 }
143 137
144 138 protected onObserverException(e: any) {
145 139 }
146 140
147 141 private _removeOnce(d: IObserver<T>) {
148 142 const i = this._once.indexOf(d);
149 143 if (i >= 0)
150 144 this._once.splice(i, 1);
151 145 }
152 146
153 147 private _removeObserver(d: IObserver<T>) {
154 148 const i = this._observers.indexOf(d);
155 149 if (i >= 0)
156 150 this._observers.splice(i, 1);
157 151 }
158 152
159 153 private _notify(guard: (observer: IObserver<T>) => void) {
160 154 this._once.forEach(guard);
161 155 this._once = [];
162 156
163 157 this._observers.forEach(guard);
164 158 }
165 159
166 160 protected _notifyNext(evt: T) {
167 161 const guard = (observer: IObserver<T>) => {
168 162 try {
169 163 observer.next(evt);
170 164 } catch (e) {
171 165 this.onObserverException(e);
172 166 }
173 167 };
174 168
175 169 this._notify(guard);
176 170 }
177 171
178 172 protected _notifyError(e: any) {
179 173 const guard = (observer: IObserver<T>) => {
180 174 try {
181 175 observer.error(e);
182 176 } catch (e) {
183 177 this.onObserverException(e);
184 178 }
185 179 };
186 180
187 181 this._notify(guard);
188 182 this._observers = [];
189 183 this._complete = true;
190 184 }
191 185
192 186 protected _notifyCompleted() {
193 187 const guard = (observer: IObserver<T>) => {
194 188 try {
195 189 observer.complete();
196 190 } catch (e) {
197 191 this.onObserverException(e);
198 192 }
199 193 };
200 194
201 195 this._notify(guard);
202 196 this._observers = [];
203 197 this._complete = true;
204 198 }
205 199 }
@@ -1,130 +1,133
1 1 import { TraceSource } from "../log/TraceSource";
2 import { argumentNotNull, argumentNotEmptyString } from "../safe";
3 import { Descriptor, ContainerServiceMap, ContainerKeys, ContainerResolve } from "./interfaces";
2 import { argumentNotEmptyString } from "../safe";
3 import { Descriptor, ContainerServiceMap, ContainerKeys, TypeOfService } from "./interfaces";
4 4 import { Container } from "./Container";
5 5 import { MapOf } from "../interfaces";
6 6
7 7 const trace = TraceSource.get("@implab/core/di/ActivationContext");
8 8
9 9 export interface ActivationContextInfo {
10 10 name: string;
11 11
12 12 service: string;
13 13
14 14 }
15 15
16 16 export class ActivationContext<S extends object> {
17 17 _cache: MapOf<any>;
18 18
19 19 _services: ContainerServiceMap<S>;
20 20
21 21 _visited: MapOf<any>;
22 22
23 23 _name: string;
24 24
25 25 _service: Descriptor<S, any>;
26 26
27 27 _container: Container<S>;
28 28
29 29 _parent: ActivationContext<S> | undefined;
30 30
31 31 constructor(container: Container<S>, services: ContainerServiceMap<S>, name: string, service: Descriptor<S, any>) {
32 32 this._name = name;
33 33 this._service = service;
34 34 this._visited = {};
35 35 this._cache = {};
36 36 this._services = services;
37 37 this._container = container;
38 38 }
39 39
40 40 getName() {
41 41 return this._name;
42 42 }
43 43
44 44 getContainer() {
45 45 return this._container;
46 46 }
47 47
48 resolve<K extends ContainerKeys<S>>(name: K, def?: ContainerResolve<S, K>) {
48 resolve<K extends ContainerKeys<S>>(name: K): TypeOfService<S, K>;
49 resolve<K extends ContainerKeys<S>, T>(name: K, def: T): TypeOfService<S, K> | T;
50 resolve<K extends ContainerKeys<S>>(name: K, def: undefined): TypeOfService<S, K> | undefined;
51 resolve<K extends ContainerKeys<S>, T>(name: K, def?: T): TypeOfService<S, K> | T | undefined {
49 52 const d = this._services[name];
50 53
51 54 if (d !== undefined) {
52 55 return this.activate(d, name.toString());
53 56 } else {
54 if (def !== undefined && def !== null)
57 if (arguments.length > 1)
55 58 return def;
56 59 else
57 60 throw new Error(`Service ${name} not found`);
58 61 }
59 62 }
60 63
61 64 /**
62 65 * registers services local to the the activation context
63 66 *
64 67 * @name{string} the name of the service
65 68 * @service{string} the service descriptor to register
66 69 */
67 70 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>) {
68 71 argumentNotEmptyString(name, "name");
69 72
70 73 this._services[name] = service as any;
71 74 }
72 75
73 76 has(id: string) {
74 77 return id in this._cache;
75 78 }
76 79
77 80 get<T>(id: string) {
78 81 return this._cache[id];
79 82 }
80 83
81 84 store(id: string, value: any) {
82 85 return (this._cache[id] = value);
83 86 }
84 87
85 88 activate<T>(d: Descriptor<S, T>, name: string) {
86 89 if (trace.isLogEnabled())
87 90 trace.log(`enter ${name} ${d}`);
88 91
89 92 const ctx = this.enter(d, name);
90 93 const v = d.activate(ctx);
91 94
92 95 if (trace.isLogEnabled())
93 96 trace.log(`leave ${name}`);
94 97
95 98 return v;
96 99 }
97 100
98 101 visit(id: string) {
99 102 const count = this._visited[id] || 0;
100 103 this._visited[id] = count + 1;
101 104 return count;
102 105 }
103 106
104 107 getStack(): ActivationContextInfo[] {
105 108 const stack = [{
106 109 name: this._name,
107 110 service: this._service.toString()
108 111 }];
109 112
110 113 return this._parent ?
111 114 stack.concat(this._parent.getStack()) :
112 115 stack;
113 116 }
114 117
115 118 private enter(service: Descriptor<S, any>, name: string): this {
116 119 const clone = Object.create(this);
117 120 clone._name = name;
118 121 clone._services = Object.create(this._services);
119 122 clone._parent = this;
120 123 clone._service = service;
121 124 return clone;
122 125 }
123 126
124 127 /** Creates a clone for the current context, used to protect it from modifications */
125 128 clone(): this {
126 129 const clone = Object.create(this);
127 130 clone._services = Object.create(this._services);
128 131 return clone;
129 132 }
130 133 }
@@ -1,30 +1,25
1 1 import { TypeRegistration } from "./Configuration";
2 2 import { ExtractDependency } from "./fluent/interfaces";
3 import { RegistrationBuilder } from "./fluent/RegistrationBuilder";
4 3
5 export class AnnotaionBuilder<T, S extends object> {
4 export class AnnotationBuilder<T, S extends object> {
6 5 wire<P extends any[]>(...args: P) {
7 6 return <C extends new (...args: ExtractDependency<P, S>) => T>(constructor: C) => {
8 7
9 8 };
10 9 }
11 10
12 11 inject<P extends any[]>(...args: P) {
13 12 return <X extends { [m in M]: (...args: any) => any }, M extends keyof (T | X)>(
14 13 target: X,
15 14 memberName: M,
16 15 descriptor: TypedPropertyDescriptor< T[M] extends ((...args: ExtractDependency<P, S>) => any) ? any : never >
17 16 ) => {
18 17
19 18 };
20 19 }
21 20
22 21 getDescriptor(): TypeRegistration<new () => T, S> {
23 22 throw new Error();
24 23 }
25 24
26 getRegistrationBuilder(): RegistrationBuilder<T, S> {
27 throw new Error();
28 25 }
29
30 }
@@ -1,428 +1,446
1 1 import {
2 2 PartialServiceMap,
3 3 ActivationType,
4 4 ContainerKeys,
5 ContainerResolve
5 TypeOfService,
6 ILifetimeManager
6 7 } from "./interfaces";
7 8
8 9 import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get, primitive } from "../safe";
9 10 import { AggregateDescriptor } from "./AggregateDescriptor";
10 11 import { ValueDescriptor } from "./ValueDescriptor";
11 12 import { Container } from "./Container";
12 13 import { ReferenceDescriptor } from "./ReferenceDescriptor";
13 14 import { TypeServiceDescriptor } from "./TypeServiceDescriptor";
14 15 import { FactoryServiceDescriptor } from "./FactoryServiceDescriptor";
15 16 import { TraceSource } from "../log/TraceSource";
16 17 import { ConfigError } from "./ConfigError";
17 18 import { Cancellation } from "../Cancellation";
18 19 import { makeResolver } from "./ResolverHelper";
19 20 import { ICancellation } from "../interfaces";
20 21 import { isDescriptor } from "./traits";
21 22 import { LazyReferenceDescriptor } from "./LazyReferenceDescriptor";
22 23 import { LifetimeManager } from "./LifetimeManager";
23 24
24 25 export interface RegistrationScope<S extends object> {
25 26
26 27 /** сервисы, которые регистрируются в контексте активации и таким образом
27 28 * могут переопределять ранее зарегистрированные сервисы. за это свойство
28 29 * нужно платить, кроме того порядок активации будет влиять на результат
29 30 * разрешения зависимостей.
30 31 */
31 32 services?: RegistrationMap<S>;
32 33 }
33 34
34 35 /**
35 * Базовый интефейс конфигурации сервисов
36 * Базовый интерфейс конфигурации сервисов
36 37 */
37 38 export interface ServiceRegistration<T, S extends object> extends RegistrationScope<S> {
38 39
39 40 activation?: ActivationType;
40 41
41 42 params?: any;
42 43
44 /** Специальный идентификатор используется при активации singleton, если
45 * не указан для TypeRegistration вычисляется как oid($type)
46 */
47 typeId?: string;
48
43 49 inject?: object | object[];
44 50
45 51 cleanup?: ((instance: T) => void) | string;
46 52 }
47 53
48 54 export interface TypeRegistration<C extends new (...args: any[]) => any, S extends object> extends ServiceRegistration<InstanceType<C>, S> {
49 55 $type: string | C;
50 56 params?: Registration<ConstructorParameters<C>, S>;
51 57 }
52 58
53 59 export interface StrictTypeRegistration<C extends new (...args: any[]) => any, S extends object> extends ServiceRegistration<InstanceType<C>, S> {
54 60 $type: C;
55 61 params?: Registration<ConstructorParameters<C>, S>;
56 62 }
57 63
58 64 export interface FactoryRegistration<F extends (...args: any[]) => any, S extends object> extends ServiceRegistration<ReturnType<F>, S> {
59 65 $factory: string | F;
60 66 }
61 67
62 68 export interface ValueRegistration<T> {
63 69 $value: T;
64 70 parse?: boolean;
65 71 }
66 72
67 73 export interface DependencyRegistration<S extends object, K extends ContainerKeys<S> = ContainerKeys<S>> extends RegistrationScope<S> {
68 74 $dependency: K;
69 75 lazy?: boolean;
70 76 optional?: boolean;
71 default?: ContainerResolve<S, K>;
77 default?: TypeOfService<S, K>;
72 78 }
73 79
74 80 export interface LazyDependencyRegistration<S extends object, K extends ContainerKeys<S> = ContainerKeys<S>> extends DependencyRegistration<S, K> {
75 81 lazy: true;
76 82 }
77 83
78 84 export type Registration<T, S extends object> = T extends primitive ? T :
79 85 (
80 86 T |
81 87 { [k in keyof T]: Registration<T[k], S> } |
82 88 TypeRegistration<new (...args: any[]) => T, S> |
83 89 FactoryRegistration<(...args: any[]) => T, S> |
84 90 ValueRegistration<any> |
85 91 DependencyRegistration<S, keyof S>
86 92 );
87 93
88 94 export type RegistrationMap<S extends object> = {
89 95 [k in keyof S]?: Registration<S[k], S>;
90 96 };
91 97
92 98 const _activationTypes: { [k in ActivationType]: number; } = {
93 99 singleton: 1,
94 100 container: 2,
95 101 hierarchy: 3,
96 102 context: 4,
97 103 call: 5
98 104 };
99 105
100 106 export function isTypeRegistration(x: any): x is TypeRegistration<new () => any, any> {
101 107 return (!isPrimitive(x)) && ("$type" in x);
102 108 }
103 109
104 110 export function isFactoryRegistration(x: any): x is FactoryRegistration<() => any, any> {
105 111 return (!isPrimitive(x)) && ("$factory" in x);
106 112 }
107 113
108 114 export function isValueRegistration(x: any): x is ValueRegistration<any> {
109 115 return (!isPrimitive(x)) && ("$value" in x);
110 116 }
111 117
112 118 export function isDependencyRegistration<S extends object>(x: any): x is DependencyRegistration<S, keyof S> {
113 119 return (!isPrimitive(x)) && ("$dependency" in x);
114 120 }
115 121
116 122 export function isActivationType(x: string): x is ActivationType {
117 123 return typeof x === "string" && x in _activationTypes;
118 124 }
119 125
120 126 const trace = TraceSource.get("@implab/core/di/Configuration");
121 127 async function mapAll(data: any[], map?: (v: any, k: number) => any): Promise<any[]>;
122 128 async function mapAll(data: any, map?: (v: any, k: string) => any): Promise<any>;
123 129 async function mapAll(data: any, map?: (v: any, k: any) => any): Promise<any> {
124 130 if (data instanceof Array) {
125 131 return Promise.all(map ? data.map(map) : data);
126 132 } else {
127 133 const keys = Object.keys(data);
128 134
129 135 const o: any = {};
130 136
131 137 await Promise.all(keys.map(async k => {
132 138 const v = map ? map(data[k], k) : data[k];
133 139 o[k] = isPromise(v) ? await v : v;
134 140 }));
135 141
136 142 return o;
137 143 }
138 144 }
139 145
140 146 export type ModuleResolver = (moduleName: string, ct?: ICancellation) => any;
141 147
142 148 export class Configuration<S extends object> {
143 149
144 150 _hasInnerDescriptors = false;
145 151
146 152 readonly _container: Container<S>;
147 153
148 154 _path: Array<string>;
149 155
150 156 _configName: string | undefined;
151 157
152 158 _require: ModuleResolver | undefined;
153 159
154 160 constructor(container: Container<S>) {
155 161 argumentNotNull(container, "container");
156 162 this._container = container;
157 163 this._path = [];
158 164 }
159 165
160 166 async loadConfiguration(moduleName: string, contextRequire?: any, ct = Cancellation.none) {
161 167 argumentNotEmptyString(moduleName, "moduleName");
162 168
163 169 trace.log(
164 170 "loadConfiguration moduleName={0}, contextRequire={1}",
165 171 moduleName,
166 172 contextRequire ? typeof (contextRequire) : "<nil>"
167 173 );
168 174
169 175 this._configName = moduleName;
170 176
171 177 const r = await makeResolver(undefined, contextRequire);
172 178
173 179 const config = await r(moduleName, ct);
174 180
175 181 await this._applyConfiguration(
176 182 config,
177 183 await makeResolver(moduleName, contextRequire),
178 184 ct
179 185 );
180 186 }
181 187
182 188 async applyConfiguration(data: RegistrationMap<S>, contextRequire?: any, ct = Cancellation.none) {
183 189 argumentNotNull(data, "data");
184 190
185 191 await this._applyConfiguration(data, await makeResolver(void (0), contextRequire), ct);
186 192 }
187 193
188 194 async _applyConfiguration(data: RegistrationMap<S>, resolver?: ModuleResolver, ct = Cancellation.none) {
189 195 trace.log("applyConfiguration");
190 196
191 197 this._configName = "$";
192 198
193 199 if (resolver)
194 200 this._require = resolver;
195 201
196 202 let services: PartialServiceMap<S>;
197 203
198 204 try {
199 205 services = await this._visitRegistrations(data, "$");
200 206 } catch (e) {
201 207 throw this._makeError(e);
202 208 }
203 209
204 210 this._container.register(services);
205 211 }
206 212
207 213 _makeError(inner: any) {
208 214 const e = new ConfigError("Failed to load configuration", inner);
209 215 e.configName = this._configName || "<inline>";
210 216 e.path = this._makePath();
211 217 return e;
212 218 }
213 219
214 220 _makePath() {
215 221 return this._path
216 222 .reduce(
217 223 (prev, cur) => typeof cur === "number" ?
218 224 `${prev}[${cur}]` :
219 225 `${prev}.${cur}`
220 226 )
221 227 .toString();
222 228 }
223 229
224 230 async _resolveType(moduleName: string, localName: string) {
225 231 trace.log("resolveType moduleName={0}, localName={1}", moduleName, localName);
226 232 try {
227 233 const m = await this._loadModule(moduleName);
228 return localName ? get(localName, m) : m;
234 if (localName) {
235 return get(localName, m);
236 } else {
237 if (m instanceof Function)
238 return m;
239 if ("default" in m)
240 return m.default;
241 return m;
242 }
229 243 } catch (e) {
230 244 trace.error("Failed to resolve type moduleName={0}, localName={1}", moduleName, localName);
231 245 throw e;
232 246 }
233 247 }
234 248
235 249 _loadModule(moduleName: string) {
236 250 trace.debug("loadModule {0}", moduleName);
237 251 if (!this._require)
238 252 throw new Error("Module loader isn't specified");
239 253
240 254 return this._require(moduleName);
241 255 }
242 256
243 257 async _visitRegistrations(data: RegistrationMap<S>, name: string) {
244 258 this._enter(name);
245 259
246 260 if (data.constructor &&
247 261 data.constructor.prototype !== Object.prototype)
248 262 throw new Error("Configuration must be a simple object");
249 263
250 264 const services = await mapAll(data, async (v, k) => {
251 265 const d = await this._visit(v, k.toString());
252 266 return isDescriptor(d) ? d : new AggregateDescriptor(d);
253 267 }) as PartialServiceMap<S>;
254 268
255 269 this._leave();
256 270
257 271 return services;
258 272 }
259 273
260 274 _enter(name: string) {
261 275 this._path.push(name.toString());
262 276 trace.debug(">{0}", name);
263 277 }
264 278
265 279 _leave() {
266 280 const name = this._path.pop();
267 281 trace.debug("<{0}", name);
268 282 }
269 283
270 284 async _visit(data: any, name: string): Promise<any> {
271 285 if (isPrimitive(data) || isDescriptor(data))
272 286 return data;
273 287
274 288 if (isDependencyRegistration<S>(data)) {
275 289 return this._visitDependencyRegistration(data, name);
276 290 } else if (isValueRegistration(data)) {
277 291 return this._visitValueRegistration(data, name);
278 292 } else if (isTypeRegistration(data)) {
279 293 return this._visitTypeRegistration(data, name);
280 294 } else if (isFactoryRegistration(data)) {
281 295 return this._visitFactoryRegistration(data, name);
282 296 } else if (data instanceof Array) {
283 297 return this._visitArray(data, name);
284 298 }
285 299
286 300 return this._visitObject(data, name);
287 301 }
288 302
289 303 async _visitObject(data: any, name: string) {
290 304 if (data.constructor &&
291 305 data.constructor.prototype !== Object.prototype)
292 306 return new ValueDescriptor(data);
293 307
294 308 this._enter(name);
295 309
296 310 const v = await mapAll(data, delegate(this, "_visit"));
297 311
298 312 // TODO: handle inline descriptors properly
299 313 // const ex = {
300 314 // activate(ctx) {
301 315 // const value = ctx.activate(this.prop, "prop");
302 316 // // some code
303 317 // },
304 318 // // will be turned to ReferenceDescriptor
305 319 // prop: { $dependency: "depName" }
306 320 // };
307 321
308 322 this._leave();
309 323 return v;
310 324 }
311 325
312 326 async _visitArray(data: any[], name: string) {
313 327 if (data.constructor &&
314 328 data.constructor.prototype !== Array.prototype)
315 329 return new ValueDescriptor(data);
316 330
317 331 this._enter(name);
318 332
319 333 const v = await mapAll(data, delegate(this, "_visit"));
320 334 this._leave();
321 335
322 336 return v;
323 337 }
324 338
325 339 _makeServiceParams(data: ServiceRegistration<any, S>) {
326 340 const opts: any = {
327 341 };
328 342 if (data.services)
329 343 opts.services = this._visitRegistrations(data.services, "services");
330 344
331 345 if (data.inject) {
332 346 this._enter("inject");
333 347 opts.inject = mapAll(
334 348 data.inject instanceof Array ?
335 349 data.inject :
336 350 [data.inject],
337 351 delegate(this, "_visitObject")
338 352 );
339 353 this._leave();
340 354 }
341 355
342 356 if ("params" in data)
343 357 opts.params = data.params instanceof Array ?
344 358 this._visitArray(data.params, "params") :
345 359 this._visit(data.params, "params");
346 360
347 361 if (data.activation) {
348 opts.activation = this._getLifetimeManager(data.activation);
362 opts.activation = this._getLifetimeManager(data.activation, data.typeId);
349 363 }
350 364
351 365 if (data.cleanup)
352 366 opts.cleanup = data.cleanup;
353 367
354 368 return opts;
355 369 }
356 370
357 371 async _visitValueRegistration<T>(data: ValueRegistration<T>, name: string) {
358 372 this._enter(name);
359 373 const d = data.parse ? new AggregateDescriptor(data.$value) : new ValueDescriptor(data.$value);
360 374 this._leave();
361 375 return d;
362 376 }
363 377
364 378 async _visitDependencyRegistration<K extends keyof S>(data: DependencyRegistration<S, K>, name: string) {
365 379 argumentNotEmptyString(data && data.$dependency, "data.$dependency");
366 380 this._enter(name);
367 381 const options = {
368 382 name: data.$dependency,
369 383 optional: data.optional,
370 384 default: data.default,
371 385 services: data.services && await this._visitRegistrations(data.services, "services")
372 386 };
373 387 const d = data.lazy ? new LazyReferenceDescriptor<S, K>(options) : new ReferenceDescriptor<S, K>(options);
374 388 this._leave();
375 389 return d;
376 390 }
377 391
378 392 async _visitTypeRegistration(data: TypeRegistration<new () => any, S>, name: string) {
379 393 argumentNotNull(data.$type, "data.$type");
380 394 this._enter(name);
381 395
382 396 const opts = this._makeServiceParams(data);
383 397 if (data.$type instanceof Function) {
384 398 opts.type = data.$type;
385 399 } else {
386 400 const [moduleName, typeName] = data.$type.split(":", 2);
387 opts.type = this._resolveType(moduleName, typeName);
401 const t = opts.type = this._resolveType(moduleName, typeName);
402 if (!(t instanceof Function))
403 throw Error("$type (" + data.$type + ") is not a constructable");
388 404 }
389 405
390 406 const d = new TypeServiceDescriptor<S, any, any[]>(
391 407 await mapAll(opts)
392 408 );
393 409
394 410 this._leave();
395 411
396 412 return d;
397 413 }
398 414
399 415 async _visitFactoryRegistration(data: FactoryRegistration<() => any, S>, name: string) {
400 416 argumentOfType(data.$factory, Function, "data.$factory");
401 417 this._enter(name);
402 418
403 419 const opts = this._makeServiceParams(data);
404 420 opts.factory = data.$factory;
405 421
406 422 const d = new FactoryServiceDescriptor<S, any, any[]>(
407 423 await mapAll(opts)
408 424 );
409 425
410 426 this._leave();
411 427 return d;
412 428 }
413 429
414 _getLifetimeManager(activation: ActivationType) {
430 _getLifetimeManager(activation: ActivationType, typeId: string | undefined): ILifetimeManager {
415 431 switch (activation) {
416 432 case "container":
417 433 return this._container.getLifetimeManager();
418 434 case "hierarchy":
419 435 return LifetimeManager.hierarchyLifetime;
420 436 case "context":
421 437 return LifetimeManager.contextLifetime;
422 438 case "singleton":
423 return LifetimeManager.singletonLifetime;
439 if (typeId === undefined)
440 throw Error("The singleton activation requires a typeId");
441 return LifetimeManager.singletonLifetime(typeId);
424 442 default:
425 443 return LifetimeManager.empty;
426 444 }
427 445 }
428 446 }
@@ -1,135 +1,132
1 1 import { ActivationContext } from "./ActivationContext";
2 2 import { ValueDescriptor } from "./ValueDescriptor";
3 3 import { ActivationError } from "./ActivationError";
4 import { ServiceMap, Descriptor, PartialServiceMap, ContainerProvided, Resolver, ContainerServiceMap, ContainerKeys, ContainerResolve, ILifetimeManager } from "./interfaces";
4 import { ServiceMap, Descriptor, PartialServiceMap, ContainerProvided, ServiceLocator, ContainerServiceMap, ContainerKeys, TypeOfService, ILifetimeManager } from "./interfaces";
5 5 import { TraceSource } from "../log/TraceSource";
6 6 import { Configuration, RegistrationMap } from "./Configuration";
7 7 import { Cancellation } from "../Cancellation";
8 8 import { MapOf, IDestroyable } from "../interfaces";
9 9 import { isDescriptor } from "./traits";
10 10 import { LifetimeManager } from "./LifetimeManager";
11 11 import { each } from "../safe";
12 12
13 13 const trace = TraceSource.get("@implab/core/di/ActivationContext");
14 14
15 export class Container<S extends object = any> implements Resolver<S>, IDestroyable {
15 export class Container<S extends object = any> implements ServiceLocator<S>, IDestroyable {
16 16 readonly _services: ContainerServiceMap<S>;
17 17
18 readonly _cache: MapOf<any>;
19
20 18 readonly _lifetimeManager: ILifetimeManager;
21 19
22 20 readonly _cleanup: (() => void)[];
23 21
24 22 readonly _root: Container<S>;
25 23
26 24 readonly _parent?: Container<S>;
27 25
28 26 _disposed: boolean;
29 27
30 28 constructor(parent?: Container<S>) {
31 29 this._parent = parent;
32 30 this._services = parent ? Object.create(parent._services) : {};
33 this._cache = {};
34 31 this._cleanup = [];
35 32 this._root = parent ? parent.getRootContainer() : this;
36 33 this._services.container = new ValueDescriptor(this) as any;
37 34 this._disposed = false;
38 35 this._lifetimeManager = new LifetimeManager();
39 36 }
40 37
41 38 getRootContainer() {
42 39 return this._root;
43 40 }
44 41
45 42 getParent() {
46 43 return this._parent;
47 44 }
48 45
49 46 getLifetimeManager() {
50 47 return this._lifetimeManager;
51 48 }
52 49
53 resolve<K extends ContainerKeys<S>>(name: K, def?: ContainerResolve<S, K>): ContainerResolve<S, K> {
50 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K> {
54 51 trace.debug("resolve {0}", name);
55 52 const d = this._services[name];
56 53 if (d === undefined) {
57 54 if (def !== undefined)
58 55 return def;
59 56 else
60 57 throw new Error("Service '" + name + "' isn't found");
61 58 } else {
62 59
63 60 const context = new ActivationContext<S>(this, this._services, String(name), d);
64 61 try {
65 62 return d.activate(context);
66 63 } catch (error) {
67 64 throw new ActivationError(name.toString(), context.getStack(), error);
68 65 }
69 66 }
70 67 }
71 68
72 69 /**
73 70 * @deprecated use resolve() method
74 71 */
75 getService<K extends ContainerKeys<S>>(name: K, def?: ContainerResolve<S, K>) {
72 getService<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>) {
76 73 return this.resolve(name, def);
77 74 }
78 75
79 76 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>): this;
80 77 register(services: PartialServiceMap<S>): this;
81 78 register<K extends keyof S>(nameOrCollection: K | ServiceMap<S>, service?: Descriptor<S, S[K]>) {
82 79 if (arguments.length === 1) {
83 80 const data = nameOrCollection as ServiceMap<S>;
84 81
85 82 each(data, (v, k) => this.register(k, v));
86 83 } else {
87 84 if (!isDescriptor(service))
88 85 throw new Error("The service parameter must be a descriptor");
89 86
90 87 this._services[nameOrCollection as K] = service as any;
91 88 }
92 89 return this;
93 90 }
94 91
95 92 onDispose(callback: () => void) {
96 93 if (!(callback instanceof Function))
97 94 throw new Error("The callback must be a function");
98 95 this._cleanup.push(callback);
99 96 }
100 97
101 98 destroy() {
102 99 return this.dispose();
103 100 }
104 101 dispose() {
105 102 if (this._disposed)
106 103 return;
107 104 this._disposed = true;
108 105 for (const f of this._cleanup)
109 106 f();
110 107 }
111 108
112 109 /**
113 110 * @param{String|Object} config
114 111 * The configuration of the contaier. Can be either a string or an object,
115 112 * if the configuration is an object it's treated as a collection of
116 113 * services which will be registed in the contaier.
117 114 *
118 115 * @param{Function} opts.contextRequire
119 116 * The function which will be used to load a configuration or types for services.
120 117 *
121 118 */
122 119 async configure(config: string | RegistrationMap<S>, opts?: any, ct = Cancellation.none) {
123 120 const c = new Configuration<S>(this);
124 121
125 122 if (typeof (config) === "string") {
126 123 return c.loadConfiguration(config, opts && opts.contextRequire, ct);
127 124 } else {
128 125 return c.applyConfiguration(config, opts && opts.contextRequire, ct);
129 126 }
130 127 }
131 128
132 129 createChildContainer<S2 extends object = S>(): Container<S & S2> {
133 130 return new Container<S & S2>(this as any);
134 131 }
135 132 }
@@ -1,84 +1,83
1 1 import { argumentNotEmptyString, each } from "../safe";
2 2 import { ActivationContext } from "./ActivationContext";
3 import { Descriptor, PartialServiceMap, ContainerResolve, ContainerKeys } from "./interfaces";
3 import { Descriptor, PartialServiceMap, TypeOfService, ContainerKeys } from "./interfaces";
4 4 import { ActivationError } from "./ActivationError";
5 5
6 6 export interface ReferenceDescriptorParams<S extends object, K extends ContainerKeys<S>> {
7 7 name: K;
8 8 optional?: boolean;
9 default?: ContainerResolve<S, K>;
9 default?: TypeOfService<S, K>;
10 10 services?: PartialServiceMap<S>;
11 11 }
12 12
13 13 export class LazyReferenceDescriptor<S extends object = any, K extends ContainerKeys<S> = ContainerKeys<S>>
14 implements Descriptor<S, ((args?: PartialServiceMap<S>) => ContainerResolve<S, K>)> {
14 implements Descriptor<S, ((args?: PartialServiceMap<S>) => TypeOfService<S, K>)> {
15 15
16 16 _name: K;
17 17
18 18 _optional = false;
19 19
20 _default: ContainerResolve<S, K> | undefined;
20 _default: TypeOfService<S, K> | undefined;
21 21
22 22 _services: PartialServiceMap<S>;
23 23
24 24 constructor(opts: ReferenceDescriptorParams<S, K>) {
25 25 argumentNotEmptyString(opts && opts.name, "opts.name");
26 26 this._name = opts.name;
27 27 this._optional = !!opts.optional;
28 28 this._default = opts.default;
29 29
30 30 this._services = (opts.services || {}) as PartialServiceMap<S>;
31 31 }
32 32
33 33 activate(context: ActivationContext<S>) {
34 34 // добавляем сервисы
35 35 if (this._services) {
36 36 each(this._services, (v, k) => context.register(k, v));
37 37 }
38 38
39 39 const saved = context.clone();
40 40
41 41 return (cfg?: PartialServiceMap<S>) => {
42 42 // защищаем контекст на случай исключения в процессе
43 43 // активации
44 44 const ct = cfg ? saved.clone() : saved;
45 45 try {
46 46 if (cfg) {
47 47 each(cfg, (v, k) => ct.register(k, v));
48 48 }
49 49
50 50 return this._optional ? ct.resolve(this._name, this._default) : ct
51 51 .resolve(this._name);
52 52 } catch (error) {
53 53 throw new ActivationError(this._name.toString(), ct.getStack(), error);
54 54 }
55 55 };
56
57 56 }
58 57
59 58 toString() {
60 59 const opts = [];
61 60 if (this._optional)
62 61 opts.push("optional");
63 62
64 63 opts.push("lazy");
65 64
66 65 const parts = [
67 66 "@ref "
68 67 ];
69 68 if (opts.length) {
70 69 parts.push("{");
71 70 parts.push(opts.join());
72 71 parts.push("} ");
73 72 }
74 73
75 74 parts.push(this._name.toString());
76 75
77 76 if (this._default !== undefined && this._default !== null) {
78 77 parts.push(" = ");
79 78 parts.push(String(this._default));
80 79 }
81 80
82 81 return parts.join("");
83 82 }
84 83 }
@@ -1,142 +1,132
1 1 import { IDestroyable, MapOf } from "../interfaces";
2 2 import { argumentNotNull, isDestroyable } from "../safe";
3 3 import { ILifetimeManager, ILifetime } from "./interfaces";
4 4 import { ActivationContext } from "./ActivationContext";
5 5
6 6 function safeCall(item: () => void) {
7 7 try {
8 8 item();
9 9 } catch {
10 10 // silence!
11 11 }
12 12 }
13 13
14 14 const emptyLifetime: ILifetime = {
15 15 has() {
16 16 return false;
17 17 },
18 18
19 19 enter() {
20 20
21 21 },
22 22
23 23 get() {
24 24 throw new Error("The specified item isn't registered with this lifetime manager");
25 25 },
26 26
27 27 store() {
28 28 // does nothing
29 29 }
30 30
31 31 };
32 32
33 let nextId = 0;
34
33 35 export class LifetimeManager implements IDestroyable, ILifetimeManager {
34 36 private _cleanup: (() => void)[] = [];
35 37 private _cache: MapOf<any> = {};
36 38 private _destroyed = false;
37 39
38 40 private _pending: MapOf<boolean> = {};
39 41
40 initialize(id: string): ILifetime {
42 initialize(): ILifetime {
41 43 const self = this;
44 const id = ++nextId;
42 45 return {
43 46 has() {
44 47 return (id in self._cache);
45 48 },
46 49
47 50 get() {
48 51 const t = self._cache[id];
49 52 if (t === undefined)
50 53 throw new Error(`The item with with the key ${id} isn't found`);
51 54 return t;
52 55 },
53 56
54 57 enter() {
55 58 if (self._pending[id])
56 59 throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
57 60 self._pending[id] = true;
58 61 },
59 62
60 63 store(item: any, cleanup?: (item: any) => void) {
61 64 argumentNotNull(id, "id");
62 65 argumentNotNull(item, "item");
63 66
64 67 if (this.has())
65 68 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
66 69 delete self._pending[id];
67 70
68 71 self._cache[id] = item;
69 72
70 73 if (self._destroyed)
71 74 throw new Error("Lifetime manager is destroyed");
72 75 if (cleanup) {
73 76 self._cleanup.push(() => cleanup(item));
74 77 } else if (isDestroyable(item)) {
75 78 self._cleanup.push(() => item.destroy());
76 79 }
77 80 }
78 81 };
79 82 }
80 83
81 84 destroy() {
82 85 if (!this._destroyed) {
83 86 this._destroyed = true;
84 87 this._cleanup.forEach(safeCall);
85 88 this._cleanup.length = 0;
86 89 }
87 90 }
88 91
89 92 static readonly empty: ILifetimeManager = {
90 93 initialize(): ILifetime {
91 94 return emptyLifetime;
92 },
93 destroy() {
94 throw new Error("Trying to destroy empty lifetime manager, this is a bug.");
95 95 }
96
97 96 };
98 97
99 98 static readonly hierarchyLifetime: ILifetimeManager = {
100 initialize(id: string, context: ActivationContext<any>): ILifetime {
101 return context.getContainer().getLifetimeManager().initialize(id, context);
102 },
103 destroy() {
104 throw new Error("Trying to destroy hierarchy lifetime manager, this is a bug.");
105 }
106 };
107
108 static readonly singletonLifetime: ILifetimeManager = {
109 initialize(id: string): ILifetime {
110 return singletonLifetimeManager.initialize(id);
111 },
112 destroy() {
113 throw new Error("Trying to destroy singleton lifetime manager, this is a bug.");
99 initialize(context: ActivationContext<any>): ILifetime {
100 return context.getContainer().getLifetimeManager().initialize(context);
114 101 }
115 102 };
116 103
117 104 static readonly contextLifetime: ILifetimeManager = {
118 initialize(id: string, context: ActivationContext<any>): ILifetime {
105 initialize(context: ActivationContext<any>): ILifetime {
106 const id = String(++nextId);
119 107 return {
120 108 enter() {
121 109 if (context.visit(id))
122 110 throw new Error("Cyclic reference detected");
123 111 },
124 112 get() {
125 113 return context.get(id);
126 114 },
127 115 has() {
128 116 return context.has(id);
129 117 },
130 118 store(item: any) {
131 119 context.store(id, item);
132 120 }
133
121 };
122 }
134 123 };
135 },
136 destroy() {
137 throw new Error("Trying to destroy empty lifetime manager, this is a bug.");
124
125 static singletonLifetime(typeId: string): ILifetimeManager {
126 return {
127 initialize() {
128 return emptyLifetime;
138 129 }
139 130 };
140 131 }
141
142 const singletonLifetimeManager = new LifetimeManager();
132 }
@@ -1,68 +1,68
1 1 import { argumentNotEmptyString, each } from "../safe";
2 2 import { ActivationContext } from "./ActivationContext";
3 import { Descriptor, PartialServiceMap, ContainerResolve, ContainerKeys } from "./interfaces";
3 import { Descriptor, PartialServiceMap, TypeOfService, ContainerKeys } from "./interfaces";
4 4
5 5 export interface ReferenceDescriptorParams<S extends object, K extends ContainerKeys<S>> {
6 6 name: K;
7 7 optional?: boolean;
8 default?: ContainerResolve<S, K>;
8 default?: TypeOfService<S, K>;
9 9 services?: PartialServiceMap<S>;
10 10 }
11 11
12 12 export class ReferenceDescriptor<S extends object = any, K extends ContainerKeys<S> = ContainerKeys<S>>
13 implements Descriptor<S, ContainerResolve<S, K>> {
13 implements Descriptor<S, TypeOfService<S, K>> {
14 14
15 15 _name: K;
16 16
17 17 _optional = false;
18 18
19 _default: ContainerResolve<S, K> | undefined;
19 _default: TypeOfService<S, K> | undefined;
20 20
21 21 _services: PartialServiceMap<S>;
22 22
23 23 constructor(opts: ReferenceDescriptorParams<S, K>) {
24 24 argumentNotEmptyString(opts && opts.name, "opts.name");
25 25 this._name = opts.name;
26 26 this._optional = !!opts.optional;
27 27 this._default = opts.default;
28 28
29 29 this._services = (opts.services || {}) as PartialServiceMap<S>;
30 30 }
31 31
32 32 activate(context: ActivationContext<S>) {
33 33 // добавляем сервисы
34 34 if (this._services) {
35 35 each(this._services, (v, k) => context.register(k, v));
36 36 }
37 37
38 38 const res = this._optional ?
39 39 context.resolve(this._name, this._default) :
40 40 context.resolve(this._name);
41 41
42 42 return res;
43 43 }
44 44
45 45 toString() {
46 46 const opts = [];
47 47 if (this._optional)
48 48 opts.push("optional");
49 49
50 50 const parts = [
51 51 "@ref "
52 52 ];
53 53 if (opts.length) {
54 54 parts.push("{");
55 55 parts.push(opts.join());
56 56 parts.push("} ");
57 57 }
58 58
59 59 parts.push(this._name.toString());
60 60
61 61 if (this._default !== undefined && this._default !== null) {
62 62 parts.push(" = ");
63 63 parts.push(String(this._default));
64 64 }
65 65
66 66 return parts.join("");
67 67 }
68 68 }
@@ -1,157 +1,157
1 1 import { ActivationContext } from "./ActivationContext";
2 import { Descriptor, ServiceMap, PartialServiceMap, ILifetimeManager } from "./interfaces";
3 import { argumentNotNull, isPrimitive, keys, isNull } from "../safe";
2 import { Descriptor, ServiceMap, PartialServiceMap, ILifetimeManager, ILifetime } from "./interfaces";
3 import { isPrimitive, keys, isNull } from "../safe";
4 4 import { TraceSource } from "../log/TraceSource";
5 5 import { isDescriptor } from "./traits";
6 6 import { LifetimeManager } from "./LifetimeManager";
7 7 import { MatchingMemberKeys } from "../interfaces";
8 import { Container } from "./Container";
9
10 let cacheId = 0;
11 8
12 9 const trace = TraceSource.get("@implab/core/di/ActivationContext");
13 10
14 11 function injectMethod<T, M extends keyof T, S extends object, A>(target: T, method: M, context: ActivationContext<S>, args: A) {
15 12
16 13 const m = target[method];
17 14 if (!m || typeof m !== "function")
18 15 throw new Error("Method '" + method + "' not found");
19 16
20 17 if (args instanceof Array)
21 18 return m.apply(target, _parse(args, context, "." + method));
22 19 else
23 20 return m.call(target, _parse(args, context, "." + method));
24 21 }
25 22
26 23 function makeCleanupCallback<T>(method: Cleaner<T>) {
27 24 if (typeof (method) === "function") {
28 25 return (target: T) => {
29 26 method(target);
30 27 };
31 28 } else {
32 29 return (target: T) => {
33 30 const m = target[method] as any;
34 31 m.apply(target);
35 32 };
36 33 }
37 34 }
38 35
39 36 function _parse(value: any, context: ActivationContext<any>, path: string): any {
40 37 if (isPrimitive(value))
41 38 return value as any;
42 39
43 40 trace.debug("parse {0}", path);
44 41
45 42 if (isDescriptor(value))
46 43 return context.activate(value, path);
47 44
48 45 if (value instanceof Array)
49 46 return value.map((x, i) => _parse(x, context, `${path}[${i}]`)) as any;
50 47
51 48 const t: any = {};
52 49
53 50 keys(value).forEach(p => t[p] = _parse(value[p], context, `${path}.${p}`));
54 51
55 52 return t;
56 53 }
57 54
58 55 export type Cleaner<T> = ((x: T) => void) | MatchingMemberKeys<() => void, T>;
59 56
60 57 export type InjectionSpec<T> = {
61 58 [m in keyof T]?: any;
62 59 };
63 60
64 61 export interface ServiceDescriptorParams<S extends object, T, P extends any[]> {
65 62 lifetime?: ILifetimeManager;
66 63
67 64 params?: P;
68 65
69 66 inject?: InjectionSpec<T>[];
70 67
71 68 services?: PartialServiceMap<S>;
72 69
73 70 cleanup?: Cleaner<T>;
74 71 }
75 72
76 73 export class ServiceDescriptor<S extends object, T, P extends any[]> implements Descriptor<S, T> {
77 74 _services: ServiceMap<S>;
78 75
79 76 _params: P | undefined;
80 77
81 78 _inject: InjectionSpec<T>[];
82 79
83 80 _cleanup: ((item: T) => void) | undefined;
84 81
85 _cacheId = String(++cacheId);
82 _lifetimeManager = LifetimeManager.empty;
86 83
87 _lifetime = LifetimeManager.empty;
84 _objectLifetime: ILifetime | undefined;
88 85
89 86 constructor(opts: ServiceDescriptorParams<S, T, P>) {
90 87
91 88 if (opts.lifetime)
92 this._lifetime = opts.lifetime;
89 this._lifetimeManager = opts.lifetime;
93 90
94 91 if (!isNull(opts.params))
95 92 this._params = opts.params;
96 93
97 94 this._inject = opts.inject || [];
98 95
99 96 this._services = (opts.services || {}) as ServiceMap<S>;
100 97
101 98 if (opts.cleanup) {
102 99 if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function))
103 100 throw new Error(
104 101 "The cleanup parameter must be either a function or a function name");
105 102
106 103 this._cleanup = makeCleanupCallback(opts.cleanup);
107 104 }
108 105 }
109 106
110 107 activate(context: ActivationContext<S>) {
111 const lifetime = this._lifetime.initialize(this._cacheId, context);
108 if (!this._objectLifetime)
109 this._objectLifetime = this._lifetimeManager.initialize(context);
110
111 const lifetime = this._objectLifetime;
112 112
113 113 if (lifetime.has()) {
114 114 return lifetime.get();
115 115 } else {
116 116 lifetime.enter();
117 117 const instance = this._create(context);
118 lifetime.store(this._cacheId, this._cleanup);
118 lifetime.store(instance, this._cleanup);
119 119 return instance;
120 120 }
121 121 }
122 122
123 123 _factory(...params: any[]): T {
124 124 throw Error("Not implemented");
125 125 }
126 126
127 127 _create(context: ActivationContext<S>) {
128 128 trace.debug(`constructing ${context._name}`);
129 129
130 130 if (this._services) {
131 131 keys(this._services).forEach(p => context.register(p, this._services[p]));
132 132 }
133 133
134 134 let instance: T;
135 135
136 136 if (this._params === undefined) {
137 137 instance = this._factory();
138 138 } else if (this._params instanceof Array) {
139 139 instance = this._factory.apply(this, _parse(this._params, context, "args"));
140 140 } else {
141 141 instance = this._factory(_parse(this._params, context, "args"));
142 142 }
143 143
144 144 if (this._inject) {
145 145 this._inject.forEach(spec => {
146 146 for (const m in spec)
147 147 injectMethod(instance, m, context, spec[m]);
148 148 });
149 149 }
150 150 return instance;
151 151 }
152 152
153 153 clone() {
154 154 return Object.create(this);
155 155 }
156 156
157 157 }
@@ -1,71 +1,48
1 1 import { primitive } from "../../safe";
2 import { ActivationType } from "../interfaces";
3 import { AnnotaionBuilder } from "../Annotations";
4 import { LazyDependencyRegistration, DependencyRegistration } from "../Configuration";
5 import { Container } from "../Container";
2 import { AnnotationBuilder } from "../Annotations";
3 import { ILifetime, TypeOfService, ContainerKeys } from "../interfaces";
6 4
7 export interface DependencyOptions<T> {
5 export interface DependencyOptions {
8 6 optional?: boolean;
9 default?: T;
7 default?: any;
10 8 }
11 9
12 export interface LazyDependencyOptions<T> extends DependencyOptions<T> {
10 export interface LazyDependencyOptions extends DependencyOptions {
13 11 lazy: true;
14 12 }
15 13
16 export type ExtractService<K, S> = K extends keyof S ? S[K] : K;
14 export type ExtractService<K, S> = K extends keyof S ? S[K] : never;
17 15
18 16 export type ExtractDependency<D, S> = D extends { $dependency: infer K } ?
19 17 D extends { lazy: true } ? () => ExtractService<K, S> : ExtractService<K, S> :
20 18 D extends { $type: new (...args: any[]) => infer I } ? I :
21 19 D extends { $factory: (...args: any[]) => infer R } ? R :
22 20 WalkDependencies<D, S>;
23 21
24 22 export type WalkDependencies<D, S> = D extends primitive ? D :
25 23 { [K in keyof D]: ExtractDependency<D[K], S> };
26 24
27 25 export type ServiceModule<T, S extends object, M extends keyof any = "service"> = {
28 [m in M]: AnnotaionBuilder<T, S>;
26 [m in M]: AnnotationBuilder<T, S>;
29 27 };
30 28
31 export interface ServiceRecordBuilder<T, S extends object> {
32 type<P extends any[], C extends new (...args: ExtractDependency<P, S>) => T>(
33 target: C, ...params: P): ConstructorBuilder<C, S>;
34 factory<P extends any[], F extends (...args: ExtractDependency<P, S>) => T>(
35 target: F, ...params: P): FactoryBuilder<F, S>;
36 wired<M extends keyof any>(module: ServiceModule<T, S, M>, m: M): RegistrationBuilder<T, S>;
37 wired(module: ServiceModule<T, S>): RegistrationBuilder<T, S>;
29 export type InferReferenceType<S extends object, K extends keyof ContainerKeys<S>, O> = O extends { default: infer X } ? (TypeOfService<S, K> | X) :
30 O extends { optional: true } ? (TypeOfService<S, K> | undefined) :
31 TypeOfService<S, K>;
32
33 export interface Resolver<S extends object> {
34 <K extends keyof ContainerKeys<S>, O extends LazyDependencyOptions>(this: void, name: K, opts: O): () => InferReferenceType<S, K, O>;
35 <K extends keyof ContainerKeys<S>, O extends DependencyOptions>(this: void, name: K, opts?: O): InferReferenceType<S, K, O>;
38 36 }
39 37
40 export interface RegistrationVisitor {
41 visitDependency(): void;
42
43 visitObject(): void;
44
45 visitTypeRegistration(): void;
38 export interface DescriptorBuilder<T, S extends object> {
39 service(service: AnnotationBuilder<T, S> | ServiceModule<T, S>): void;
46 40
47 visitFactoryRegistration(): void;
41 factory(f: (resolve: Resolver<S>, activate: <T2>(lifetime: ILifetime, factory: () => T2, cleanup?: (item: T2) => void) => T2) => T): void;
48 42
49 }
50
51 export interface ServiceRegistration {
52 visit(visitor: RegistrationVisitor): void;
43 value(v: T): void;
53 44 }
54 45
55 export interface ConfigBuilder<S extends object, Y extends keyof S = keyof S> {
56 register<K extends Y>(name: K, builder: (t: ServiceRecordBuilder<S[K], S>) => void | Promise<void>): ConfigBuilder<S, Exclude<Y, K>>;
57 register<K extends Y, V>(name: S[K] extends ExtractDependency<V, S> ? K : never, value: V): ConfigBuilder<S, Exclude<Y, K>>;
58 register<K extends Y>(name: K, value: S[K], raw: true): ConfigBuilder<S, Exclude<Y, K>>;
59
60 apply(container: Container<S>): Promise<void>;
46 export interface Configuration<S extends object, Y extends keyof S = keyof S> {
47 register<K extends Y>(name: K, builder: (d: DescriptorBuilder<S[K], S>) => void): Configuration<S, Exclude<Y, K>>;
61 48 }
62
63 export interface ServicesDeclaration<S extends object> {
64 build<T>(this: void): ServiceRecordBuilder<T, S>;
65 annotate<T>(this: void): AnnotaionBuilder<T, S>;
66
67 dependency<K extends keyof S>(this: void, name: K, opts: LazyDependencyOptions<S[K]>): LazyDependencyRegistration<S, K>;
68 dependency<K extends keyof S>(this: void, name: K, opts?: DependencyOptions<S[K]>): DependencyRegistration<S, K>;
69
70 configure(): ConfigBuilder<S>;
71 }
@@ -1,53 +1,57
1 1 import { ActivationContext } from "./ActivationContext";
2 import { IDestroyable } from "../interfaces";
3 2
4 3 export interface Descriptor<S extends object = any, T = any> {
5 4 activate(context: ActivationContext<S>): T;
6 5 }
7 6
8 7 export type ServiceMap<S extends object> = {
9 8 [k in keyof S]: Descriptor<S, S[k]>;
10 9 };
11 10
12 11 export type ContainerKeys<S extends object> = keyof S | keyof ContainerProvided<S>;
13 12
14 export type ContainerResolve<S extends object, K> =
13 export type TypeOfService<S extends object, K> =
15 14 K extends keyof ContainerProvided<S> ? ContainerProvided<S>[K] :
16 15 K extends keyof S ? S[K] : never;
17 16
18 17 export type ContainerServiceMap<S extends object> = {
19 [K in ContainerKeys<S>]: Descriptor<S, ContainerResolve<S, K>>;
18 [K in ContainerKeys<S>]: Descriptor<S, TypeOfService<S, K>>;
20 19 };
21 20
22 21 export type PartialServiceMap<S extends object> = {
23 22 [k in keyof S]?: Descriptor<S, S[k]>;
24 23 };
25 24
26 export interface Resolver<S extends object> {
27 resolve<K extends ContainerKeys<S>>(name: K, def?: ContainerResolve<S, K>): ContainerResolve<S, K>;
25 export interface ServiceLocator<S extends object> {
26 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K>;
28 27 }
29 28
30 29 export interface ContainerProvided<S extends object> {
31 container: Resolver<S>;
30 container: ServiceLocator<S>;
32 31 }
33 32
34 33 export type ContainerRegistered<S extends object> = /*{
35 34 [K in Exclude<keyof S, keyof ContainerProvided<S>>]: S[K];
36 35 };*/
37 36 Exclude<S, ContainerProvided<S>>;
38 37
39 38 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
40 39
41 export interface ILifetimeManager extends IDestroyable {
42 initialize(id: string, context: ActivationContext<any>): ILifetime;
40 export interface ILifetimeManager {
41 initialize(context: ActivationContext<any>): ILifetime;
43 42 }
44 43
44 /**
45 * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет
46 * свой собственный объект `ILifetime`, который создается при первой активации
47 */
45 48 export interface ILifetime {
49 /** Проверяет, что уже создан экземпляр объекта */
46 50 has(): boolean;
47 51
48 52 get(): any;
49 53
50 54 enter(): void;
51 55
52 56 store(item: any, cleanup?: (item: any) => void): void;
53 57 }
@@ -1,50 +1,23
1 1 import { isPrimitive } from "../safe";
2 2 import { Descriptor } from "./interfaces";
3 import { ServicesDeclaration, ServiceRecordBuilder, ServiceModule, RegistrationBuilder, ExtractDependency } from "./fluent/interfaces";
4 import { AnnotaionBuilder } from "./Annotations";
5 import { FactoryBuilder } from "./fluent/FactoryBuilder";
6 import { ConstructorBuilder } from "./fluent/ConstructorBuiler";
3 import { AnnotationBuilder } from "./Annotations";
4 import { Configuration } from "./fluent/Configuration";
7 5
8 6 export function isDescriptor(x: any): x is Descriptor {
9 7 return (!isPrimitive(x)) &&
10 8 (x.activate instanceof Function);
11 9 }
12 10
13 export function declare<S extends object>(): ServicesDeclaration<S> {
11 export function declare<S extends object>() {
14 12 return {
15 13 annotate<T>() {
16 return new AnnotaionBuilder<T, S>();
17 },
18 build<T>(): ServiceRecordBuilder<T, S> {
19 return {
20 factory<P extends any[], F extends (...args: ExtractDependency<P, S>) => T>(
21 target: F,
22 ...params: P
23 ): FactoryBuilder<F, S> {
24 return new FactoryBuilder(target, params);
14 return new AnnotationBuilder<T, S>();
25 15 },
26
27 type<P extends any[], C extends new (...args: ExtractDependency<P, S>) => T>(
28 target: C, ...params: P
29 ): ConstructorBuilder<C, S> {
30 return new ConstructorBuilder(target, params);
31 },
32
33 wired<M extends keyof any>(module: ServiceModule<T, S, M>, m?: M): RegistrationBuilder<T, S> {
34 const service = m ?
35 module[m] :
36 (module as ServiceModule<T, S>).service;
37 if (!service)
38 throw new Error("The specified module doen's provides a service annotation");
39 return service.getRegistrationBuilder();
40 }
41 };
42 },
43 configure() {
16 configure(): Configuration<S> {
44 17 throw new Error();
45 18 },
46 19 dependency() {
47 20 throw new Error();
48 21 }
49 22 };
50 23 }
@@ -1,17 +1,20
1 import { configure, dependency, build } from "./services";
1 import { configure } from "./services";
2 2
3 3 export const config = configure()
4 .register("bar", async s => s.wired(await import("./Bar"), "service"))
5 .register("box", s => import("./Box").then(m => s.wired(m)))
6 .register("host", "example.com")
7 // .registerType("bar2", Bar, [{ foo: dependency("foo"), host: "" }]);
8 .register("bar2", async s => s.type((await import("./Bar")).Bar,
9 {
10 foo: build().type((await import("./Foo")).Foo)
11 .activate("context"),
12 nested: { lazy: dependency("foo", { lazy: true }) },
13 host: dependency("host")
4 .register("host", s => s.value("example.com"))
5 .register("bar2", bar2 => Promise.all([import("./Foo"), import("./Bar")])
6 .then(([{ Foo }, { Bar }]) => {
7 const lifetime: any = undefined; // new HierarchyLifetime()
8 bar2.factory((resolve, activate) => {
9 const bar = new Bar({
10 foo: activate(lifetime, () => new Foo()),
11 nested: {
12 lazy: resolve("foo", { lazy: true })
14 13 },
15 "")
16 .inject("setName", dependency("host"))
14 host: resolve("host")
15 }, "some text");
16 bar.setName(resolve("host"));
17 return bar;
18 });
19 })
17 20 );
@@ -1,25 +1,25
1 1 import { Foo } from "./Foo";
2 2 import { Bar } from "./Bar";
3 3 import { Box } from "./Box";
4 import { declare } from "../di/fluent/interfaces";
4 import { declare } from "../di/traits";
5 5
6 6 /**
7 7 * Сервисы доступные внутри контейнера
8 8 */
9 9 export interface Services {
10 10 foo: Foo;
11 11
12 12 bar: Bar;
13 13
14 14 bar2: Bar;
15 15
16 16 box: Box<Bar>;
17 17
18 18 host: string;
19 19
20 20 }
21 21
22 22 /**
23 23 * Экспортируем вспомогательные функции для описания сервисов и кинфогурации
24 24 */
25 export const { dependency, build, annotate, configure } = declare<Services>();
25 export const { dependency, annotate, configure } = declare<Services>();
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now