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