##// END OF EJS Templates
working on fluent configuration, di annotations removed
cin -
r134:511bcc634d65 ioc ts support
parent child
Show More
@@ -1,110 +1,109
1 1 define([
2 2 "dojo/_base/declare",
3 3 "../safe",
4 4 "dojo/when",
5 5 "dojo/store/util/QueryResults" ],
6 6
7 7 function(declare, safe, when, QueryResults) {
8 8
9 9 "use strict";
10 10
11 11 /**
12 12 * Обертка вокруг произвольного хранилища, только для чтения. Используется
13 13 * для преобразования данных, например, отображения в списках элементов
14 14 * пространственных данных.
15 15 */
16 16 return declare(null, {
17 17 /**
18 18 * @type{String} Свойство, хранящее идентификатор
19 19 */
20 20 idProperty : null,
21 21
22 22 _store : null,
23 23
24 24 /**
25 25 * @param{String} opts.idProperty Имя свойства, в которое будет записан
26 26 * идентификатор, если не указан, то идентификатор будет
27 27 * взят из родительского хранилища или использоваться
28 28 * строка <code>id</code>
29 29 * @param{dojo.store} opts.store Родительское хранилище
30 30 */
31 31 constructor : function(opts) {
32 32 safe.argumentNotNull(opts, "opts");
33 33 safe.argumentNotNull(opts.store, "opts.store");
34 34
35 35 this._store = opts.store;
36 36 delete opts.store;
37 37 declare.safeMixin(this, opts);
38 38 this.idProperty = opts.idProperty || this._store.idProperty || "id";
39 39 },
40 40
41 41 getParentStore : function() {
42 42 return this._store;
43 43 },
44 44
45 45 get : function(id) {
46 46 var me = this;
47 47 return when(me._store.get(id), function(x) {
48 48 var m = me.mapItem(x);
49 49 if (!(me.idProperty in m))
50 50 m[me.idProperty] = id;
51 51 return m;
52 52 });
53 53 },
54 54
55 55 /**
56 56 * Выполняет запрос в родительском хранилище, для этого используется
57 57 * <code>translateQuery</code> для подготовки запроса, затем,
58 58 * <code>mapItem</code> для преобразования результатов.
59 59 */
60 60 query : function(q, options) {
61 61 var me = this, store = this._store;
62 62 return when(store.query(me.translateQuery(q), me
63 63 .translateOptions(options)), function(res) {
64 64 var total = res.total;
65 65 var mapped = res.map(function(x) {
66 66 var m = me.mapItem(x);
67 67 if (!(me.idProperty in m))
68 68 m[me.idProperty] = store.getIdentity &&
69 69 store.getIdentity(x);
70 70 return m;
71 71 });
72 72 mapped.total = total;
73 73 var results = new QueryResults(mapped);
74 console.log(results);
75 74 return results;
76 75 });
77 76 },
78 77
79 78 getIdentity : function(obj) {
80 79 return obj && obj[this.idProperty];
81 80 },
82 81
83 82 /**
84 83 * Преобразование запроса в формат родительского хранилища.
85 84 *
86 85 * @param{Object} q Запрос в формате текущего хранилища
87 86 * @returns{Object} Запрос в формате родительского хранилища
88 87 */
89 88 translateQuery : function(q) {
90 89 return q;
91 90 },
92 91
93 92 translateOptions : function(options) {
94 93 return options;
95 94 },
96 95
97 96 /**
98 97 * Преобразование объекта из родительского хранилища. При преобразовании
99 98 * в объекте можно задать идентификатор, иначе идентификатор будет
100 99 * автоматически получен и присвоен из родительского хранилища
101 100 *
102 101 * @param{Object} item Объект из родительского хранилища
103 102 * @returns{Object} результат преобразования
104 103 */
105 104 mapItem : function(item) {
106 105 return item;
107 106 }
108 107 });
109 108
110 109 }); No newline at end of file
@@ -1,40 +1,41
1 1 import { Cancellation } from "../Cancellation";
2 2 import { IAsyncComponent, ICancellation, ICancellable, IDestroyable } from "../interfaces";
3 3 import { destroy } from "../safe";
4 4
5 const noop = () => void (0);
6
5 7 export class AsyncComponent implements IAsyncComponent, ICancellable {
6 _cancel: ((e: any) => void) | undefined;
8 _cancel: ((e: any) => void) = noop;
7 9
8 10 _completion: Promise<void> = Promise.resolve();
9 11
10 12 getCompletion() { return this._completion; }
11 13
12 14 runOperation(op: (ct: ICancellation) => any, ct: ICancellation = Cancellation.none) {
13 15 // create inner cancellation bound to the passed cancellation token
14 16 let h: IDestroyable;
15 17 const inner = new Cancellation(cancel => {
16 18
17 19 this._cancel = cancel;
18 20 h = ct.register(cancel);
19 21 });
20 22
21 23 // TODO create cancellation source here
22 24 const guard = async () => {
23 25 try {
24 26 await op(inner);
25 27 } finally {
26 28 // after the operation is complete we need to cleanup the
27 29 // resources
28 30 destroy(h);
29 this._cancel = undefined;
31 this._cancel = noop;
30 32 }
31 33 };
32 34
33 35 return this._completion = guard();
34 36 }
35 37
36 38 cancel(reason: any) {
37 if (this._cancel)
38 39 this._cancel(reason);
39 40 }
40 41 }
@@ -1,133 +1,140
1 1 import { TraceSource } from "../log/TraceSource";
2 2 import { argumentNotEmptyString } from "../safe";
3 import { Descriptor, ContainerServiceMap, ContainerKeys, TypeOfService } from "./interfaces";
3 import { Descriptor, ContainerServiceMap, ContainerKeys, TypeOfService, ILifetime } from "./interfaces";
4 4 import { Container } from "./Container";
5 5 import { MapOf } from "../interfaces";
6 6
7 7 const trace = TraceSource.get("@implab/core/di/ActivationContext");
8 8
9 9 export interface ActivationContextInfo {
10 10 name: string;
11 11
12 12 service: string;
13 13
14 14 }
15 15
16 let nextId = 1;
17
16 18 export class ActivationContext<S extends object> {
17 19 _cache: MapOf<any>;
18 20
19 21 _services: ContainerServiceMap<S>;
20 22
21 23 _visited: MapOf<any>;
22 24
23 25 _name: string;
24 26
25 27 _service: Descriptor<S, any>;
26 28
27 29 _container: Container<S>;
28 30
29 31 _parent: ActivationContext<S> | undefined;
30 32
31 33 constructor(container: Container<S>, services: ContainerServiceMap<S>, name: string, service: Descriptor<S, any>) {
32 34 this._name = name;
33 35 this._service = service;
34 36 this._visited = {};
35 37 this._cache = {};
36 38 this._services = services;
37 39 this._container = container;
38 40 }
39 41
40 42 getName() {
41 43 return this._name;
42 44 }
43 45
44 46 getContainer() {
45 47 return this._container;
46 48 }
47 49
48 50 resolve<K extends ContainerKeys<S>>(name: K): TypeOfService<S, K>;
49 51 resolve<K extends ContainerKeys<S>, T>(name: K, def: T): TypeOfService<S, K> | T;
50 52 resolve<K extends ContainerKeys<S>>(name: K, def: undefined): TypeOfService<S, K> | undefined;
51 53 resolve<K extends ContainerKeys<S>, T>(name: K, def?: T): TypeOfService<S, K> | T | undefined {
52 54 const d = this._services[name];
53 55
54 56 if (d !== undefined) {
55 57 return this.activate(d, name.toString());
56 58 } else {
57 59 if (arguments.length > 1)
58 60 return def;
59 61 else
60 62 throw new Error(`Service ${name} not found`);
61 63 }
62 64 }
63 65
64 66 /**
65 67 * registers services local to the the activation context
66 68 *
67 69 * @name{string} the name of the service
68 70 * @service{string} the service descriptor to register
69 71 */
70 72 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>) {
71 73 argumentNotEmptyString(name, "name");
72 74
73 75 this._services[name] = service as any;
74 76 }
75 77
76 has(id: string) {
77 return id in this._cache;
78 createLifetime(): ILifetime {
79 const id = nextId++;
80 const me = this;
81 return {
82 initialize() {
83 },
84 has() {
85 return id in me._cache;
86 },
87 get() {
88 return me._cache[id];
89 },
90 store(item: any) {
91 me._cache[id] = item;
78 92 }
79
80 get<T>(id: string) {
81 return this._cache[id];
93 };
82 94 }
83
84 store(id: string, value: any) {
85 return (this._cache[id] = value);
86 }
87
88 95 activate<T>(d: Descriptor<S, T>, name: string) {
89 96 if (trace.isLogEnabled())
90 97 trace.log(`enter ${name} ${d}`);
91 98
92 99 const ctx = this.enter(d, name);
93 100 const v = d.activate(ctx);
94 101
95 102 if (trace.isLogEnabled())
96 103 trace.log(`leave ${name}`);
97 104
98 105 return v;
99 106 }
100 107
101 108 visit(id: string) {
102 109 const count = this._visited[id] || 0;
103 110 this._visited[id] = count + 1;
104 111 return count;
105 112 }
106 113
107 114 getStack(): ActivationContextInfo[] {
108 115 const stack = [{
109 116 name: this._name,
110 117 service: this._service.toString()
111 118 }];
112 119
113 120 return this._parent ?
114 121 stack.concat(this._parent.getStack()) :
115 122 stack;
116 123 }
117 124
118 125 private enter(service: Descriptor<S, any>, name: string): this {
119 126 const clone = Object.create(this);
120 127 clone._name = name;
121 128 clone._services = Object.create(this._services);
122 129 clone._parent = this;
123 130 clone._service = service;
124 131 return clone;
125 132 }
126 133
127 134 /** Creates a clone for the current context, used to protect it from modifications */
128 135 clone(): this {
129 136 const clone = Object.create(this);
130 137 clone._services = Object.create(this._services);
131 138 return clone;
132 139 }
133 140 }
@@ -1,446 +1,450
1 1 import {
2 2 PartialServiceMap,
3 3 ActivationType,
4 4 ContainerKeys,
5 5 TypeOfService,
6 ILifetimeManager
6 ILifetime
7 7 } from "./interfaces";
8 8
9 9 import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get, primitive } from "../safe";
10 10 import { AggregateDescriptor } from "./AggregateDescriptor";
11 11 import { ValueDescriptor } from "./ValueDescriptor";
12 12 import { Container } from "./Container";
13 13 import { ReferenceDescriptor } from "./ReferenceDescriptor";
14 14 import { TypeServiceDescriptor } from "./TypeServiceDescriptor";
15 15 import { FactoryServiceDescriptor } from "./FactoryServiceDescriptor";
16 16 import { TraceSource } from "../log/TraceSource";
17 17 import { ConfigError } from "./ConfigError";
18 18 import { Cancellation } from "../Cancellation";
19 19 import { makeResolver } from "./ResolverHelper";
20 20 import { ICancellation } from "../interfaces";
21 21 import { isDescriptor } from "./traits";
22 22 import { LazyReferenceDescriptor } from "./LazyReferenceDescriptor";
23 23 import { LifetimeManager } from "./LifetimeManager";
24 24
25 25 export interface RegistrationScope<S extends object> {
26 26
27 27 /** сервисы, которые регистрируются в контексте активации и таким образом
28 28 * могут переопределять ранее зарегистрированные сервисы. за это свойство
29 29 * нужно платить, кроме того порядок активации будет влиять на результат
30 30 * разрешения зависимостей.
31 31 */
32 32 services?: RegistrationMap<S>;
33 33 }
34 34
35 35 /**
36 36 * Базовый интерфейс конфигурации сервисов
37 37 */
38 38 export interface ServiceRegistration<T, S extends object> extends RegistrationScope<S> {
39 39
40 40 activation?: ActivationType;
41 41
42 42 params?: any;
43 43
44 44 /** Специальный идентификатор используется при активации singleton, если
45 45 * не указан для TypeRegistration вычисляется как oid($type)
46 46 */
47 47 typeId?: string;
48 48
49 49 inject?: object | object[];
50 50
51 51 cleanup?: ((instance: T) => void) | string;
52 52 }
53 53
54 54 export interface TypeRegistration<C extends new (...args: any[]) => any, S extends object> extends ServiceRegistration<InstanceType<C>, S> {
55 55 $type: string | C;
56 56 params?: Registration<ConstructorParameters<C>, S>;
57 57 }
58 58
59 59 export interface StrictTypeRegistration<C extends new (...args: any[]) => any, S extends object> extends ServiceRegistration<InstanceType<C>, S> {
60 60 $type: C;
61 61 params?: Registration<ConstructorParameters<C>, S>;
62 62 }
63 63
64 64 export interface FactoryRegistration<F extends (...args: any[]) => any, S extends object> extends ServiceRegistration<ReturnType<F>, S> {
65 65 $factory: string | F;
66 66 }
67 67
68 68 export interface ValueRegistration<T> {
69 69 $value: T;
70 70 parse?: boolean;
71 71 }
72 72
73 73 export interface DependencyRegistration<S extends object, K extends ContainerKeys<S> = ContainerKeys<S>> extends RegistrationScope<S> {
74 74 $dependency: K;
75 75 lazy?: boolean;
76 76 optional?: boolean;
77 77 default?: TypeOfService<S, K>;
78 78 }
79 79
80 80 export interface LazyDependencyRegistration<S extends object, K extends ContainerKeys<S> = ContainerKeys<S>> extends DependencyRegistration<S, K> {
81 81 lazy: true;
82 82 }
83 83
84 84 export type Registration<T, S extends object> = T extends primitive ? T :
85 85 (
86 86 T |
87 87 { [k in keyof T]: Registration<T[k], S> } |
88 88 TypeRegistration<new (...args: any[]) => T, S> |
89 89 FactoryRegistration<(...args: any[]) => T, S> |
90 90 ValueRegistration<any> |
91 91 DependencyRegistration<S, keyof S>
92 92 );
93 93
94 94 export type RegistrationMap<S extends object> = {
95 95 [k in keyof S]?: Registration<S[k], S>;
96 96 };
97 97
98 98 const _activationTypes: { [k in ActivationType]: number; } = {
99 99 singleton: 1,
100 100 container: 2,
101 101 hierarchy: 3,
102 102 context: 4,
103 103 call: 5
104 104 };
105 105
106 106 export function isTypeRegistration(x: any): x is TypeRegistration<new () => any, any> {
107 107 return (!isPrimitive(x)) && ("$type" in x);
108 108 }
109 109
110 110 export function isFactoryRegistration(x: any): x is FactoryRegistration<() => any, any> {
111 111 return (!isPrimitive(x)) && ("$factory" in x);
112 112 }
113 113
114 114 export function isValueRegistration(x: any): x is ValueRegistration<any> {
115 115 return (!isPrimitive(x)) && ("$value" in x);
116 116 }
117 117
118 118 export function isDependencyRegistration<S extends object>(x: any): x is DependencyRegistration<S, keyof S> {
119 119 return (!isPrimitive(x)) && ("$dependency" in x);
120 120 }
121 121
122 122 export function isActivationType(x: string): x is ActivationType {
123 123 return typeof x === "string" && x in _activationTypes;
124 124 }
125 125
126 126 const trace = TraceSource.get("@implab/core/di/Configuration");
127 127 async function mapAll(data: any[], map?: (v: any, k: number) => any): Promise<any[]>;
128 128 async function mapAll(data: any, map?: (v: any, k: string) => any): Promise<any>;
129 129 async function mapAll(data: any, map?: (v: any, k: any) => any): Promise<any> {
130 130 if (data instanceof Array) {
131 131 return Promise.all(map ? data.map(map) : data);
132 132 } else {
133 133 const keys = Object.keys(data);
134 134
135 135 const o: any = {};
136 136
137 137 await Promise.all(keys.map(async k => {
138 138 const v = map ? map(data[k], k) : data[k];
139 139 o[k] = isPromise(v) ? await v : v;
140 140 }));
141 141
142 142 return o;
143 143 }
144 144 }
145 145
146 146 export type ModuleResolver = (moduleName: string, ct?: ICancellation) => any;
147 147
148 148 export class Configuration<S extends object> {
149 149
150 150 _hasInnerDescriptors = false;
151 151
152 152 readonly _container: Container<S>;
153 153
154 154 _path: Array<string>;
155 155
156 156 _configName: string | undefined;
157 157
158 158 _require: ModuleResolver | undefined;
159 159
160 160 constructor(container: Container<S>) {
161 161 argumentNotNull(container, "container");
162 162 this._container = container;
163 163 this._path = [];
164 164 }
165 165
166 166 async loadConfiguration(moduleName: string, contextRequire?: any, ct = Cancellation.none) {
167 167 argumentNotEmptyString(moduleName, "moduleName");
168 168
169 169 trace.log(
170 170 "loadConfiguration moduleName={0}, contextRequire={1}",
171 171 moduleName,
172 172 contextRequire ? typeof (contextRequire) : "<nil>"
173 173 );
174 174
175 175 this._configName = moduleName;
176 176
177 177 const r = await makeResolver(undefined, contextRequire);
178 178
179 179 const config = await r(moduleName, ct);
180 180
181 181 await this._applyConfiguration(
182 182 config,
183 183 await makeResolver(moduleName, contextRequire),
184 184 ct
185 185 );
186 186 }
187 187
188 188 async applyConfiguration(data: RegistrationMap<S>, contextRequire?: any, ct = Cancellation.none) {
189 189 argumentNotNull(data, "data");
190 190
191 191 await this._applyConfiguration(data, await makeResolver(void (0), contextRequire), ct);
192 192 }
193 193
194 194 async _applyConfiguration(data: RegistrationMap<S>, resolver?: ModuleResolver, ct = Cancellation.none) {
195 195 trace.log("applyConfiguration");
196 196
197 197 this._configName = "$";
198 198
199 199 if (resolver)
200 200 this._require = resolver;
201 201
202 202 let services: PartialServiceMap<S>;
203 203
204 204 try {
205 205 services = await this._visitRegistrations(data, "$");
206 206 } catch (e) {
207 207 throw this._makeError(e);
208 208 }
209 209
210 210 this._container.register(services);
211 211 }
212 212
213 213 _makeError(inner: any) {
214 214 const e = new ConfigError("Failed to load configuration", inner);
215 215 e.configName = this._configName || "<inline>";
216 216 e.path = this._makePath();
217 217 return e;
218 218 }
219 219
220 220 _makePath() {
221 221 return this._path
222 222 .reduce(
223 223 (prev, cur) => typeof cur === "number" ?
224 224 `${prev}[${cur}]` :
225 225 `${prev}.${cur}`
226 226 )
227 227 .toString();
228 228 }
229 229
230 230 async _resolveType(moduleName: string, localName: string) {
231 231 trace.log("resolveType moduleName={0}, localName={1}", moduleName, localName);
232 232 try {
233 233 const m = await this._loadModule(moduleName);
234 234 if (localName) {
235 235 return get(localName, m);
236 236 } else {
237 237 if (m instanceof Function)
238 238 return m;
239 239 if ("default" in m)
240 240 return m.default;
241 241 return m;
242 242 }
243 243 } catch (e) {
244 244 trace.error("Failed to resolve type moduleName={0}, localName={1}", moduleName, localName);
245 245 throw e;
246 246 }
247 247 }
248 248
249 249 _loadModule(moduleName: string) {
250 250 trace.debug("loadModule {0}", moduleName);
251 251 if (!this._require)
252 252 throw new Error("Module loader isn't specified");
253 253
254 254 return this._require(moduleName);
255 255 }
256 256
257 257 async _visitRegistrations(data: RegistrationMap<S>, name: string) {
258 258 this._enter(name);
259 259
260 260 if (data.constructor &&
261 261 data.constructor.prototype !== Object.prototype)
262 262 throw new Error("Configuration must be a simple object");
263 263
264 264 const services = await mapAll(data, async (v, k) => {
265 265 const d = await this._visit(v, k.toString());
266 266 return isDescriptor(d) ? d : new AggregateDescriptor(d);
267 267 }) as PartialServiceMap<S>;
268 268
269 269 this._leave();
270 270
271 271 return services;
272 272 }
273 273
274 274 _enter(name: string) {
275 275 this._path.push(name.toString());
276 276 trace.debug(">{0}", name);
277 277 }
278 278
279 279 _leave() {
280 280 const name = this._path.pop();
281 281 trace.debug("<{0}", name);
282 282 }
283 283
284 async _visit(data: any, name: string): Promise<any> {
285 if (isPrimitive(data) || isDescriptor(data))
286 return data;
284 _visit(data: any, name: string): Promise<any> {
285 if (isPrimitive(data))
286 return Promise.resolve(new ValueDescriptor(data));
287 if (isDescriptor(data))
288 return Promise.resolve(data);
287 289
288 290 if (isDependencyRegistration<S>(data)) {
289 291 return this._visitDependencyRegistration(data, name);
290 292 } else if (isValueRegistration(data)) {
291 293 return this._visitValueRegistration(data, name);
292 294 } else if (isTypeRegistration(data)) {
293 295 return this._visitTypeRegistration(data, name);
294 296 } else if (isFactoryRegistration(data)) {
295 297 return this._visitFactoryRegistration(data, name);
296 298 } else if (data instanceof Array) {
297 299 return this._visitArray(data, name);
298 300 }
299 301
300 302 return this._visitObject(data, name);
301 303 }
302 304
303 305 async _visitObject(data: any, name: string) {
304 306 if (data.constructor &&
305 307 data.constructor.prototype !== Object.prototype)
306 308 return new ValueDescriptor(data);
307 309
308 310 this._enter(name);
309 311
310 312 const v = await mapAll(data, delegate(this, "_visit"));
311 313
312 314 // TODO: handle inline descriptors properly
313 315 // const ex = {
314 316 // activate(ctx) {
315 317 // const value = ctx.activate(this.prop, "prop");
316 318 // // some code
317 319 // },
318 320 // // will be turned to ReferenceDescriptor
319 321 // prop: { $dependency: "depName" }
320 322 // };
321 323
322 324 this._leave();
323 325 return v;
324 326 }
325 327
326 328 async _visitArray(data: any[], name: string) {
327 329 if (data.constructor &&
328 330 data.constructor.prototype !== Array.prototype)
329 331 return new ValueDescriptor(data);
330 332
331 333 this._enter(name);
332 334
333 335 const v = await mapAll(data, delegate(this, "_visit"));
334 336 this._leave();
335 337
336 338 return v;
337 339 }
338 340
339 341 _makeServiceParams(data: ServiceRegistration<any, S>) {
340 342 const opts: any = {
341 343 };
342 344 if (data.services)
343 345 opts.services = this._visitRegistrations(data.services, "services");
344 346
345 347 if (data.inject) {
346 348 this._enter("inject");
347 349 opts.inject = mapAll(
348 350 data.inject instanceof Array ?
349 351 data.inject :
350 352 [data.inject],
351 353 delegate(this, "_visitObject")
352 354 );
353 355 this._leave();
354 356 }
355 357
356 358 if ("params" in data)
357 359 opts.params = data.params instanceof Array ?
358 360 this._visitArray(data.params, "params") :
359 361 this._visit(data.params, "params");
360 362
361 363 if (data.activation) {
362 364 opts.activation = this._getLifetimeManager(data.activation, data.typeId);
363 365 }
364 366
365 367 if (data.cleanup)
366 368 opts.cleanup = data.cleanup;
367 369
368 370 return opts;
369 371 }
370 372
371 373 async _visitValueRegistration<T>(data: ValueRegistration<T>, name: string) {
372 374 this._enter(name);
373 375 const d = data.parse ? new AggregateDescriptor(data.$value) : new ValueDescriptor(data.$value);
374 376 this._leave();
375 377 return d;
376 378 }
377 379
378 380 async _visitDependencyRegistration<K extends keyof S>(data: DependencyRegistration<S, K>, name: string) {
379 381 argumentNotEmptyString(data && data.$dependency, "data.$dependency");
380 382 this._enter(name);
381 383 const options = {
382 384 name: data.$dependency,
383 385 optional: data.optional,
384 386 default: data.default,
385 387 services: data.services && await this._visitRegistrations(data.services, "services")
386 388 };
387 389 const d = data.lazy ? new LazyReferenceDescriptor<S, K>(options) : new ReferenceDescriptor<S, K>(options);
388 390 this._leave();
389 391 return d;
390 392 }
391 393
392 394 async _visitTypeRegistration(data: TypeRegistration<new () => any, S>, name: string) {
393 395 argumentNotNull(data.$type, "data.$type");
394 396 this._enter(name);
395 397
396 398 const opts = this._makeServiceParams(data);
397 399 if (data.$type instanceof Function) {
398 400 opts.type = data.$type;
399 401 } else {
400 402 const [moduleName, typeName] = data.$type.split(":", 2);
401 const t = opts.type = this._resolveType(moduleName, typeName);
403 opts.type = this._resolveType(moduleName, typeName).then(t => {
402 404 if (!(t instanceof Function))
403 405 throw Error("$type (" + data.$type + ") is not a constructable");
406 return t;
407 });
404 408 }
405 409
406 410 const d = new TypeServiceDescriptor<S, any, any[]>(
407 411 await mapAll(opts)
408 412 );
409 413
410 414 this._leave();
411 415
412 416 return d;
413 417 }
414 418
415 419 async _visitFactoryRegistration(data: FactoryRegistration<() => any, S>, name: string) {
416 420 argumentOfType(data.$factory, Function, "data.$factory");
417 421 this._enter(name);
418 422
419 423 const opts = this._makeServiceParams(data);
420 424 opts.factory = data.$factory;
421 425
422 426 const d = new FactoryServiceDescriptor<S, any, any[]>(
423 427 await mapAll(opts)
424 428 );
425 429
426 430 this._leave();
427 431 return d;
428 432 }
429 433
430 _getLifetimeManager(activation: ActivationType, typeId: string | undefined): ILifetimeManager {
434 _getLifetimeManager(activation: ActivationType, typeId: string | undefined): ILifetime {
431 435 switch (activation) {
432 436 case "container":
433 return this._container.getLifetimeManager();
437 return LifetimeManager.containerLifetime(this._container);
434 438 case "hierarchy":
435 return LifetimeManager.hierarchyLifetime;
439 return LifetimeManager.hierarchyLifetime();
436 440 case "context":
437 return LifetimeManager.contextLifetime;
441 return LifetimeManager.contextLifetime();
438 442 case "singleton":
439 443 if (typeId === undefined)
440 444 throw Error("The singleton activation requires a typeId");
441 445 return LifetimeManager.singletonLifetime(typeId);
442 446 default:
443 return LifetimeManager.empty;
447 return LifetimeManager.empty();
444 448 }
445 449 }
446 450 }
@@ -1,83 +1,83
1 1 import { argumentNotEmptyString, each } from "../safe";
2 2 import { ActivationContext } from "./ActivationContext";
3 3 import { Descriptor, PartialServiceMap, TypeOfService, ContainerKeys } from "./interfaces";
4 4 import { ActivationError } from "./ActivationError";
5 5
6 6 export interface ReferenceDescriptorParams<S extends object, K extends ContainerKeys<S>> {
7 7 name: K;
8 8 optional?: boolean;
9 9 default?: TypeOfService<S, K>;
10 10 services?: PartialServiceMap<S>;
11 11 }
12 12
13 13 export class LazyReferenceDescriptor<S extends object = any, K extends ContainerKeys<S> = ContainerKeys<S>>
14 14 implements Descriptor<S, ((args?: PartialServiceMap<S>) => TypeOfService<S, K>)> {
15 15
16 16 _name: K;
17 17
18 18 _optional = false;
19 19
20 20 _default: TypeOfService<S, K> | undefined;
21 21
22 22 _services: PartialServiceMap<S>;
23 23
24 24 constructor(opts: ReferenceDescriptorParams<S, K>) {
25 25 argumentNotEmptyString(opts && opts.name, "opts.name");
26 26 this._name = opts.name;
27 27 this._optional = !!opts.optional;
28 28 this._default = opts.default;
29 29
30 30 this._services = (opts.services || {}) as PartialServiceMap<S>;
31 31 }
32 32
33 33 activate(context: ActivationContext<S>) {
34 34 // добавляем сервисы
35 35 if (this._services) {
36 36 each(this._services, (v, k) => context.register(k, v));
37 37 }
38 38
39 39 const saved = context.clone();
40 40
41 return (cfg?: PartialServiceMap<S>) => {
41 return (cfg?: PartialServiceMap<S>): any => {
42 42 // защищаем контекст на случай исключения в процессе
43 43 // активации
44 44 const ct = cfg ? saved.clone() : saved;
45 45 try {
46 46 if (cfg) {
47 47 each(cfg, (v, k) => ct.register(k, v));
48 48 }
49 49
50 50 return this._optional ? ct.resolve(this._name, this._default) : ct
51 51 .resolve(this._name);
52 52 } catch (error) {
53 53 throw new ActivationError(this._name.toString(), ct.getStack(), error);
54 54 }
55 55 };
56 56 }
57 57
58 58 toString() {
59 59 const opts = [];
60 60 if (this._optional)
61 61 opts.push("optional");
62 62
63 63 opts.push("lazy");
64 64
65 65 const parts = [
66 66 "@ref "
67 67 ];
68 68 if (opts.length) {
69 69 parts.push("{");
70 70 parts.push(opts.join());
71 71 parts.push("} ");
72 72 }
73 73
74 74 parts.push(this._name.toString());
75 75
76 76 if (this._default !== undefined && this._default !== null) {
77 77 parts.push(" = ");
78 78 parts.push(String(this._default));
79 79 }
80 80
81 81 return parts.join("");
82 82 }
83 83 }
@@ -1,132 +1,176
1 1 import { IDestroyable, MapOf } from "../interfaces";
2 2 import { argumentNotNull, isDestroyable } from "../safe";
3 3 import { ILifetimeManager, ILifetime } from "./interfaces";
4 4 import { ActivationContext } from "./ActivationContext";
5 import { Container } from "./Container";
5 6
6 7 function safeCall(item: () => void) {
7 8 try {
8 9 item();
9 10 } catch {
10 11 // silence!
11 12 }
12 13 }
13 14
14 15 const emptyLifetime: ILifetime = {
15 16 has() {
16 17 return false;
17 18 },
18 19
19 enter() {
20 initialize() {
20 21
21 22 },
22 23
23 24 get() {
24 25 throw new Error("The specified item isn't registered with this lifetime manager");
25 26 },
26 27
27 28 store() {
28 29 // does nothing
29 30 }
30 31
31 32 };
32 33
34 const unknownLifetime: ILifetime = {
35 has() {
36 throw new Error("The lifetime is unknown");
37 },
38 initialize() {
39 throw new Error("Can't call initialize on the unknown lifetime object");
40 },
41 get() {
42 throw new Error("The lifetime object isn't initialized");
43 },
44 store() {
45 throw new Error("Can't store a value in the unknown lifetime object");
46 }
47 }
48
33 49 let nextId = 0;
34 50
35 51 export class LifetimeManager implements IDestroyable, ILifetimeManager {
36 52 private _cleanup: (() => void)[] = [];
37 53 private _cache: MapOf<any> = {};
38 54 private _destroyed = false;
39 55
40 56 private _pending: MapOf<boolean> = {};
41 57
42 initialize(): ILifetime {
58 create(): ILifetime {
43 59 const self = this;
44 60 const id = ++nextId;
45 61 return {
46 62 has() {
47 63 return (id in self._cache);
48 64 },
49 65
50 66 get() {
51 67 const t = self._cache[id];
52 68 if (t === undefined)
53 69 throw new Error(`The item with with the key ${id} isn't found`);
54 70 return t;
55 71 },
56 72
57 enter() {
73 initialize() {
58 74 if (self._pending[id])
59 75 throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
60 76 self._pending[id] = true;
61 77 },
62 78
63 79 store(item: any, cleanup?: (item: any) => void) {
64 80 argumentNotNull(id, "id");
65 81 argumentNotNull(item, "item");
66 82
67 83 if (this.has())
68 84 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
69 85 delete self._pending[id];
70 86
71 87 self._cache[id] = item;
72 88
73 89 if (self._destroyed)
74 90 throw new Error("Lifetime manager is destroyed");
75 91 if (cleanup) {
76 92 self._cleanup.push(() => cleanup(item));
77 93 } else if (isDestroyable(item)) {
78 94 self._cleanup.push(() => item.destroy());
79 95 }
80 96 }
81 97 };
82 98 }
83 99
84 100 destroy() {
85 101 if (!this._destroyed) {
86 102 this._destroyed = true;
87 103 this._cleanup.forEach(safeCall);
88 104 this._cleanup.length = 0;
89 105 }
90 106 }
91 107
92 static readonly empty: ILifetimeManager = {
93 initialize(): ILifetime {
108 static empty(): ILifetime {
94 109 return emptyLifetime;
95 110 }
96 };
97
98 static readonly hierarchyLifetime: ILifetimeManager = {
99 initialize(context: ActivationContext<any>): ILifetime {
100 return context.getContainer().getLifetimeManager().initialize(context);
101 }
102 };
103 111
104 static readonly contextLifetime: ILifetimeManager = {
105 initialize(context: ActivationContext<any>): ILifetime {
106 const id = String(++nextId);
112 static hierarchyLifetime(): ILifetime {
113 let _lifetime = unknownLifetime;
107 114 return {
108 enter() {
109 if (context.visit(id))
110 throw new Error("Cyclic reference detected");
115 initialize(context: ActivationContext<any>) {
116 if (_lifetime !== unknownLifetime)
117 throw new Error("Cyclic reference activation detected");
118
119 _lifetime = context.getContainer().getLifetimeManager().create(context);
111 120 },
112 121 get() {
113 return context.get(id);
122 return _lifetime.get();
114 123 },
115 124 has() {
116 return context.has(id);
125 return _lifetime.has();
117 126 },
118 store(item: any) {
119 context.store(id, item);
127 store(item: any, cleanup?: (item: any) => void) {
128 return _lifetime.store(item, cleanup);
120 129 }
121 130 };
122 131 }
132
133 static contextLifetime(): ILifetime {
134 let _lifetime = unknownLifetime;
135 return {
136 initialize(context: ActivationContext<any>) {
137 if (_lifetime !== unknownLifetime)
138 throw new Error("Cyclic reference detected");
139 _lifetime = context.createLifetime();
140 },
141 get() {
142 return _lifetime.get();
143 },
144 has() {
145 return _lifetime.has();
146 },
147 store(item: any) {
148 _lifetime.store(item);
149 }
123 150 };
151 }
124 152
125 static singletonLifetime(typeId: string): ILifetimeManager {
153 static singletonLifetime(typeId: string): ILifetime {
154 return emptyLifetime;
155 }
156
157 static containerLifetime(container: Container<any>) {
158 let _lifetime = unknownLifetime;
126 159 return {
127 initialize() {
128 return emptyLifetime;
160 initialize(context: ActivationContext<any>) {
161 if (_lifetime !== unknownLifetime)
162 throw new Error("Cyclic reference detected");
163 _lifetime = container.getLifetimeManager().create(context);
164 },
165 get() {
166 return _lifetime.get();
167 },
168 has() {
169 return _lifetime.has();
170 },
171 store(item: any) {
172 _lifetime.store(item);
129 173 }
130 174 };
131 175 }
132 176 }
@@ -1,68 +1,88
1 1 import { argumentNotEmptyString, each } from "../safe";
2 2 import { ActivationContext } from "./ActivationContext";
3 3 import { Descriptor, PartialServiceMap, TypeOfService, ContainerKeys } from "./interfaces";
4 4
5 5 export interface ReferenceDescriptorParams<S extends object, K extends ContainerKeys<S>> {
6 /**
7 * The name of the descriptor
8 */
6 9 name: K;
10
11 /**
12 * The flag that indicates that the referenced service isn't required to exist.
13 * If the reference is optional and the referenced service doesn't exist,
14 * the undefined or a default value will be returned.
15 */
7 16 optional?: boolean;
17
18 /**
19 * a default value for the reference when the referenced service doesn't exist.
20 */
8 21 default?: TypeOfService<S, K>;
22
23 /**
24 * The service overrides
25 */
9 26 services?: PartialServiceMap<S>;
10 27 }
11 28
12 29 export class ReferenceDescriptor<S extends object = any, K extends ContainerKeys<S> = ContainerKeys<S>>
13 30 implements Descriptor<S, TypeOfService<S, K>> {
14 31
15 32 _name: K;
16 33
17 34 _optional = false;
18 35
19 36 _default: TypeOfService<S, K> | undefined;
20 37
21 38 _services: PartialServiceMap<S>;
22 39
23 40 constructor(opts: ReferenceDescriptorParams<S, K>) {
24 41 argumentNotEmptyString(opts && opts.name, "opts.name");
25 42 this._name = opts.name;
26 43 this._optional = !!opts.optional;
27 44 this._default = opts.default;
28 45
29 46 this._services = (opts.services || {}) as PartialServiceMap<S>;
30 47 }
31 48
32 activate(context: ActivationContext<S>) {
49 /** This method activates the referenced service if one exists
50 * @param context activation context which is used during current activation
51 */
52 activate(context: ActivationContext<S>): any {
33 53 // добавляем сервисы
34 54 if (this._services) {
35 55 each(this._services, (v, k) => context.register(k, v));
36 56 }
37 57
38 58 const res = this._optional ?
39 59 context.resolve(this._name, this._default) :
40 60 context.resolve(this._name);
41 61
42 62 return res;
43 63 }
44 64
45 65 toString() {
46 66 const opts = [];
47 67 if (this._optional)
48 68 opts.push("optional");
49 69
50 70 const parts = [
51 71 "@ref "
52 72 ];
53 73 if (opts.length) {
54 74 parts.push("{");
55 75 parts.push(opts.join());
56 76 parts.push("} ");
57 77 }
58 78
59 79 parts.push(this._name.toString());
60 80
61 81 if (this._default !== undefined && this._default !== null) {
62 82 parts.push(" = ");
63 83 parts.push(String(this._default));
64 84 }
65 85
66 86 return parts.join("");
67 87 }
68 88 }
@@ -1,157 +1,154
1 1 import { ActivationContext } from "./ActivationContext";
2 2 import { Descriptor, ServiceMap, PartialServiceMap, ILifetimeManager, ILifetime } from "./interfaces";
3 3 import { isPrimitive, keys, isNull } from "../safe";
4 4 import { TraceSource } from "../log/TraceSource";
5 5 import { isDescriptor } from "./traits";
6 6 import { LifetimeManager } from "./LifetimeManager";
7 7 import { MatchingMemberKeys } from "../interfaces";
8 8
9 9 const trace = TraceSource.get("@implab/core/di/ActivationContext");
10 10
11 11 function injectMethod<T, M extends keyof T, S extends object, A>(target: T, method: M, context: ActivationContext<S>, args: A) {
12 12
13 13 const m = target[method];
14 14 if (!m || typeof m !== "function")
15 15 throw new Error("Method '" + method + "' not found");
16 16
17 17 if (args instanceof Array)
18 18 return m.apply(target, _parse(args, context, "." + method));
19 19 else
20 20 return m.call(target, _parse(args, context, "." + method));
21 21 }
22 22
23 23 function makeCleanupCallback<T>(method: Cleaner<T>) {
24 24 if (typeof (method) === "function") {
25 25 return (target: T) => {
26 26 method(target);
27 27 };
28 28 } else {
29 29 return (target: T) => {
30 30 const m = target[method] as any;
31 31 m.apply(target);
32 32 };
33 33 }
34 34 }
35 35
36 36 function _parse(value: any, context: ActivationContext<any>, path: string): any {
37 37 if (isPrimitive(value))
38 38 return value as any;
39 39
40 40 trace.debug("parse {0}", path);
41 41
42 42 if (isDescriptor(value))
43 43 return context.activate(value, path);
44 44
45 45 if (value instanceof Array)
46 46 return value.map((x, i) => _parse(x, context, `${path}[${i}]`)) as any;
47 47
48 48 const t: any = {};
49 49
50 50 keys(value).forEach(p => t[p] = _parse(value[p], context, `${path}.${p}`));
51 51
52 52 return t;
53 53 }
54 54
55 55 export type Cleaner<T> = ((x: T) => void) | MatchingMemberKeys<() => void, T>;
56 56
57 57 export type InjectionSpec<T> = {
58 58 [m in keyof T]?: any;
59 59 };
60 60
61 61 export interface ServiceDescriptorParams<S extends object, T, P extends any[]> {
62 lifetime?: ILifetimeManager;
62 lifetime?: ILifetime;
63 63
64 64 params?: P;
65 65
66 66 inject?: InjectionSpec<T>[];
67 67
68 68 services?: PartialServiceMap<S>;
69 69
70 70 cleanup?: Cleaner<T>;
71 71 }
72 72
73 73 export class ServiceDescriptor<S extends object, T, P extends any[]> implements Descriptor<S, T> {
74 74 _services: ServiceMap<S>;
75 75
76 76 _params: P | undefined;
77 77
78 78 _inject: InjectionSpec<T>[];
79 79
80 80 _cleanup: ((item: T) => void) | undefined;
81 81
82 _lifetimeManager = LifetimeManager.empty;
82 _lifetime = LifetimeManager.empty();
83 83
84 84 _objectLifetime: ILifetime | undefined;
85 85
86 86 constructor(opts: ServiceDescriptorParams<S, T, P>) {
87 87
88 88 if (opts.lifetime)
89 this._lifetimeManager = opts.lifetime;
89 this._lifetime = opts.lifetime;
90 90
91 91 if (!isNull(opts.params))
92 92 this._params = opts.params;
93 93
94 94 this._inject = opts.inject || [];
95 95
96 96 this._services = (opts.services || {}) as ServiceMap<S>;
97 97
98 98 if (opts.cleanup) {
99 99 if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function))
100 100 throw new Error(
101 101 "The cleanup parameter must be either a function or a function name");
102 102
103 103 this._cleanup = makeCleanupCallback(opts.cleanup);
104 104 }
105 105 }
106 106
107 107 activate(context: ActivationContext<S>) {
108 if (!this._objectLifetime)
109 this._objectLifetime = this._lifetimeManager.initialize(context);
110
111 const lifetime = this._objectLifetime;
108 const lifetime = this._lifetime;
112 109
113 110 if (lifetime.has()) {
114 111 return lifetime.get();
115 112 } else {
116 lifetime.enter();
113 lifetime.initialize(context);
117 114 const instance = this._create(context);
118 115 lifetime.store(instance, this._cleanup);
119 116 return instance;
120 117 }
121 118 }
122 119
123 120 _factory(...params: any[]): T {
124 121 throw Error("Not implemented");
125 122 }
126 123
127 124 _create(context: ActivationContext<S>) {
128 125 trace.debug(`constructing ${context._name}`);
129 126
130 127 if (this._services) {
131 128 keys(this._services).forEach(p => context.register(p, this._services[p]));
132 129 }
133 130
134 131 let instance: T;
135 132
136 133 if (this._params === undefined) {
137 134 instance = this._factory();
138 135 } else if (this._params instanceof Array) {
139 136 instance = this._factory.apply(this, _parse(this._params, context, "args"));
140 137 } else {
141 138 instance = this._factory(_parse(this._params, context, "args"));
142 139 }
143 140
144 141 if (this._inject) {
145 142 this._inject.forEach(spec => {
146 143 for (const m in spec)
147 144 injectMethod(instance, m, context, spec[m]);
148 145 });
149 146 }
150 147 return instance;
151 148 }
152 149
153 150 clone() {
154 151 return Object.create(this);
155 152 }
156 153
157 154 }
@@ -1,58 +1,54
1 import { Resolver, ServiceModule, LazyDependencyOptions, DependencyOptions } from "./interfaces";
2 import { AnnotationBuilder } from "../Annotations";
1 import { Resolver, LazyDependencyOptions, DependencyOptions } from "./interfaces";
3 2 import { Container } from "../Container";
4 3 import { Descriptor, ILifetime, ContainerKeys } from "../interfaces";
5 4 import { ActivationContext } from "../ActivationContext";
6 5
7 6 export class DescriptorBuilder<T, S extends object> {
8 7 readonly _container: Container<S>;
9 8 readonly _cb: (d: Descriptor<S, T>) => void;
10 9
11 10 constructor(container: Container<S>, cb: (d: Descriptor<S, T>) => void) {
12 11 this._container = container;
13 12 this._cb = cb;
14 13 }
15 service(service: AnnotationBuilder<T, S> | ServiceModule<T, S>) {
16
17 }
18 14
19 15 factory(f: (resolve: Resolver<S>, activate: (lifetime: ILifetime, factory: () => any, cleanup?: (item: any) => void) => any) => T): void {
20 16 this._cb({
21 17 activate(context: ActivationContext<S>) {
22 18 const resolve = (name: ContainerKeys<S>, opts?: DependencyOptions | LazyDependencyOptions) => {
23 19 if (opts && "lazy" in opts && opts.lazy) {
24 20 const c2 = context.clone();
25 21 return () => {
26 22 return opts.optional ? c2.resolve(name, opts.default) : c2.resolve(name);
27 23 };
28 24 } else {
29 25 return opts && opts.optional ? context.resolve(name, opts.default) : context.resolve(name);
30 26 }
31 27 };
32 28
33 29 const activate = (lifetime: ILifetime, factory: () => any, cleanup?: (item: any) => void) => {
34 30 if (lifetime.has()) {
35 31 return lifetime.get();
36 32 } else {
37 lifetime.enter();
33 lifetime.initialize(context);
38 34 const instance = factory();
39 35 lifetime.store(instance, cleanup);
40 36 return instance;
41 37 }
42 38
43 39 };
44 40
45 41 return f(resolve, activate);
46 42 }
47 43 });
48 44 }
49 45
50 46 value(v: T): void {
51 47 this._cb({
52 48 activate() {
53 49 return v;
54 50 }
55 51 });
56 52 }
57 53
58 54 }
@@ -1,48 +1,48
1 1 import { primitive } from "../../safe";
2 2 import { AnnotationBuilder } from "../Annotations";
3 3 import { ILifetime, TypeOfService, ContainerKeys } from "../interfaces";
4 4
5 5 export interface DependencyOptions {
6 6 optional?: boolean;
7 7 default?: any;
8 8 }
9 9
10 10 export interface LazyDependencyOptions extends DependencyOptions {
11 11 lazy: true;
12 12 }
13 13
14 14 export type ExtractService<K, S> = K extends keyof S ? S[K] : never;
15 15
16 16 export type ExtractDependency<D, S> = D extends { $dependency: infer K } ?
17 17 D extends { lazy: true } ? () => ExtractService<K, S> : ExtractService<K, S> :
18 18 D extends { $type: new (...args: any[]) => infer I } ? I :
19 19 D extends { $factory: (...args: any[]) => infer R } ? R :
20 20 WalkDependencies<D, S>;
21 21
22 22 export type WalkDependencies<D, S> = D extends primitive ? D :
23 23 { [K in keyof D]: ExtractDependency<D[K], S> };
24 24
25 25 export type ServiceModule<T, S extends object, M extends keyof any = "service"> = {
26 26 [m in M]: AnnotationBuilder<T, S>;
27 27 };
28 28
29 export type InferReferenceType<S extends object, K extends keyof ContainerKeys<S>, O> = O extends { default: infer X } ? (TypeOfService<S, K> | X) :
29 export type InferReferenceType<S extends object, K extends ContainerKeys<S>, O> = O extends { default: infer X } ? (TypeOfService<S, K> | X) :
30 30 O extends { optional: true } ? (TypeOfService<S, K> | undefined) :
31 31 TypeOfService<S, K>;
32 32
33 33 export interface Resolver<S extends object> {
34 <K extends keyof ContainerKeys<S>, O extends LazyDependencyOptions>(this: void, name: K, opts: O): () => InferReferenceType<S, K, O>;
35 <K extends keyof ContainerKeys<S>, O extends DependencyOptions>(this: void, name: K, opts?: O): InferReferenceType<S, K, O>;
34 <K extends ContainerKeys<S>, O extends LazyDependencyOptions>(this: void, name: K, opts: O): () => InferReferenceType<S, K, O>;
35 <K extends ContainerKeys<S>, O extends DependencyOptions>(this: void, name: K, opts?: O): InferReferenceType<S, K, O>;
36 36 }
37 37
38 38 export interface DescriptorBuilder<T, S extends object> {
39 39 service(service: AnnotationBuilder<T, S> | ServiceModule<T, S>): void;
40 40
41 41 factory(f: (resolve: Resolver<S>, activate: <T2>(lifetime: ILifetime, factory: () => T2, cleanup?: (item: T2) => void) => T2) => T): void;
42 42
43 43 value(v: T): void;
44 44 }
45 45
46 46 export interface Configuration<S extends object, Y extends keyof S = keyof S> {
47 47 register<K extends Y>(name: K, builder: (d: DescriptorBuilder<S[K], S>) => void): Configuration<S, Exclude<Y, K>>;
48 48 }
@@ -1,57 +1,57
1 1 import { ActivationContext } from "./ActivationContext";
2 2
3 3 export interface Descriptor<S extends object = any, T = any> {
4 4 activate(context: ActivationContext<S>): T;
5 5 }
6 6
7 7 export type ServiceMap<S extends object> = {
8 8 [k in keyof S]: Descriptor<S, S[k]>;
9 9 };
10 10
11 11 export type ContainerKeys<S extends object> = keyof S | keyof ContainerProvided<S>;
12 12
13 13 export type TypeOfService<S extends object, K> =
14 14 K extends keyof ContainerProvided<S> ? ContainerProvided<S>[K] :
15 15 K extends keyof S ? S[K] : never;
16 16
17 17 export type ContainerServiceMap<S extends object> = {
18 18 [K in ContainerKeys<S>]: Descriptor<S, TypeOfService<S, K>>;
19 19 };
20 20
21 21 export type PartialServiceMap<S extends object> = {
22 22 [k in keyof S]?: Descriptor<S, S[k]>;
23 23 };
24 24
25 25 export interface ServiceLocator<S extends object> {
26 26 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K>;
27 27 }
28 28
29 29 export interface ContainerProvided<S extends object> {
30 30 container: ServiceLocator<S>;
31 31 }
32 32
33 33 export type ContainerRegistered<S extends object> = /*{
34 34 [K in Exclude<keyof S, keyof ContainerProvided<S>>]: S[K];
35 35 };*/
36 36 Exclude<S, ContainerProvided<S>>;
37 37
38 38 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
39 39
40 40 export interface ILifetimeManager {
41 initialize(context: ActivationContext<any>): ILifetime;
41 create(context: ActivationContext<any>): ILifetime;
42 42 }
43 43
44 44 /**
45 45 * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет
46 46 * свой собственный объект `ILifetime`, который создается при первой активации
47 47 */
48 48 export interface ILifetime {
49 49 /** Проверяет, что уже создан экземпляр объекта */
50 50 has(): boolean;
51 51
52 52 get(): any;
53 53
54 enter(): void;
54 initialize(context: ActivationContext<any>): void;
55 55
56 56 store(item: any, cleanup?: (item: any) => void): void;
57 57 }
@@ -1,23 +1,12
1 1 import { isPrimitive } from "../safe";
2 2 import { Descriptor } from "./interfaces";
3 import { AnnotationBuilder } from "./Annotations";
4 3 import { Configuration } from "./fluent/Configuration";
5 4
6 5 export function isDescriptor(x: any): x is Descriptor {
7 6 return (!isPrimitive(x)) &&
8 7 (x.activate instanceof Function);
9 8 }
10 9
11 export function declare<S extends object>() {
12 return {
13 annotate<T>() {
14 return new AnnotationBuilder<T, S>();
15 },
16 configure(): Configuration<S> {
17 throw new Error();
18 },
19 dependency() {
20 throw new Error();
10 export function configure<S extends object>() {
11 return new Configuration<S>();
21 12 }
22 };
23 }
@@ -1,505 +1,500
1 1 import { ICancellable, Constructor, IDestroyable } from "./interfaces";
2 2 import { Cancellation } from "./Cancellation";
3 3
4 4 let _nextOid = 0;
5 5 const _oid = typeof Symbol === "function" ?
6 6 Symbol("__implab__oid__") :
7 7 "__implab__oid__";
8 8
9 9 export function oid(instance: any): string | undefined {
10 10 if (isNull(instance))
11 11 return undefined;
12 12
13 13 if (_oid in instance)
14 14 return instance[_oid];
15 15 else
16 16 return (instance[_oid] = "oid_" + (++_nextOid));
17 17 }
18 18
19 19 export function keys<T>(arg: T): (Extract<keyof T, string>)[] {
20 20 return isObject(arg) && arg ? Object.keys(arg) as (Extract<keyof T, string>)[] : [];
21 21 }
22 22
23 23 export function isKeyof<T>(k: string, target: T): k is Extract<keyof T, string> {
24 24 return target && typeof target === "object" && k in target;
25 25 }
26 26
27 27 export function argumentNotNull(arg: any, name: string) {
28 28 if (arg === null || arg === undefined)
29 29 throw new Error("The argument " + name + " can't be null or undefined");
30 30 }
31 31
32 32 export function argumentNotEmptyString(arg: any, name: string) {
33 33 if (typeof (arg) !== "string" || !arg.length)
34 34 throw new Error("The argument '" + name + "' must be a not empty string");
35 35 }
36 36
37 37 export function argumentNotEmptyArray(arg: any, name: string) {
38 38 if (!(arg instanceof Array) || !arg.length)
39 39 throw new Error("The argument '" + name + "' must be a not empty array");
40 40 }
41 41
42 42 export function argumentOfType(arg: any, type: Constructor<{}>, name: string) {
43 43 if (!(arg instanceof type))
44 44 throw new Error("The argument '" + name + "' type doesn't match");
45 45 }
46 46
47 47 export function isObject(val: any): val is object {
48 48 return typeof val === "object";
49 49 }
50 50
51 51 export function isNull(val: any): val is null | undefined {
52 52 return (val === null || val === undefined);
53 53 }
54 54
55 55 export type primitive = symbol | string | number | boolean | undefined | null;
56 56
57 57 export function isPrimitive(val: any): val is primitive {
58 58 return (val === null || val === undefined || typeof (val) === "string" ||
59 59 typeof (val) === "number" || typeof (val) === "boolean");
60 60 }
61 61
62 62 export function isInteger(val: any): val is number {
63 63 return parseInt(val, 10) === val;
64 64 }
65 65
66 66 export function isNumber(val: any): val is number {
67 67 return parseFloat(val) === val;
68 68 }
69 69
70 70 export function isString(val: any): val is string {
71 71 return typeof (val) === "string" || val instanceof String;
72 72 }
73 73
74 74 export function isPromise<T = any>(val: any): val is PromiseLike<T> {
75 75 return val && typeof val.then === "function";
76 76 }
77 77
78 78 export function isCancellable(val: any): val is ICancellable {
79 79 return val && typeof val.cancel === "function";
80 80 }
81 81
82 82 export function isNullOrEmptyString(val: any): val is ("" | null | undefined) {
83 83 return (val === null || val === undefined ||
84 84 ((typeof (val) === "string" || val instanceof String) && val.length === 0));
85 85 }
86 86
87 87 export function isNotEmptyArray<T = any>(arg: any): arg is T[] {
88 88 return (arg instanceof Array && arg.length > 0);
89 89 }
90 90
91 91 function _isStrictMode(this: any) {
92 92 return !this;
93 93 }
94 94
95 95 function _getNonStrictGlobal(this: any) {
96 96 return this;
97 97 }
98 98
99 99 export function getGlobal() {
100 100 // in es3 we can't use indirect call to eval, since it will
101 101 // be executed in the current call context.
102 102 if (!_isStrictMode()) {
103 103 return _getNonStrictGlobal();
104 104 } else {
105 105 // tslint:disable-next-line:no-eval
106 106 return eval.call(null, "this");
107 107 }
108 108 }
109 109
110 110 export function get(member: string, context?: object) {
111 111 argumentNotEmptyString(member, "member");
112 112 let that = context || getGlobal();
113 113 const parts = member.split(".");
114 114 for (const m of parts) {
115 115 if (!m)
116 116 continue;
117 117 if (isNull(that = that[m]))
118 118 break;
119 119 }
120 120 return that;
121 121 }
122 122
123 123 /**
124 124 * Выполняет метод для каждого элемента массива, останавливается, когда
125 125 * либо достигнут конец массива, либо функция <c>cb</c> вернула
126 126 * значение.
127 127 *
128 128 * @param {Array | Object} obj массив элементов для просмотра
129 129 * @param {Function} cb функция, вызываемая для каждого элемента
130 130 * @param {Object} thisArg значение, которое будет передано в качестве
131 131 * <c>this</c> в <c>cb</c>.
132 * @returns Результат вызова функции <c>cb</c>, либо <c>undefined</c>
133 * если достигнут конец массива.
132 * @returns {void}
134 133 */
135 134 export function each<T>(obj: T, cb: <X extends keyof T>(v: NonNullable<T[X]>, k: X) => void): void;
136 135 export function each<T>(array: T[], cb: (v: T, i: number) => void): void;
137 136 export function each(obj: any, cb: any, thisArg?: any): any;
138 137 export function each(obj: any, cb: any, thisArg?: any) {
139 138 argumentNotNull(cb, "cb");
140 139 if (obj instanceof Array) {
140 let v: any;
141 141 for (let i = 0; i < obj.length; i++) {
142 const x = cb.call(thisArg, obj[i], i);
143 if (x !== undefined)
144 return x;
142 v = obj[i];
143 if (v !== undefined)
144 cb.call(thisArg, v, i);
145 145 }
146 146 } else {
147 const _keys = Object.keys(obj);
148 for (const k of _keys) {
149 const x = cb.call(thisArg, obj[k], k);
150 if (x !== undefined)
151 return x;
152 }
147 Object.keys(obj).forEach(k => obj[k] !== undefined && cb.call(thisArg, obj[k], k));
153 148 }
154 149 }
155 150
156 151 /** Copies property values from a source object to the destination and returns
157 152 * the destination onject.
158 153 *
159 154 * @param dest The destination object into which properties from the source
160 155 * object will be copied.
161 156 * @param source The source of values which will be copied to the destination
162 157 * object.
163 158 * @param template An optional parameter specifies which properties should be
164 159 * copied from the source and how to map them to the destination. If the
165 160 * template is an array it contains the list of property names to copy from the
166 161 * source to the destination. In case of object the templates contains the map
167 162 * where keys are property names in the source and the values are property
168 163 * names in the destination object. If the template isn't specified then the
169 164 * own properties of the source are entirely copied to the destination.
170 165 *
171 166 */
172 167 export function mixin<T extends object, S extends object>(dest: T, source: S, template?: keyof S[]): T & S;
173 168 export function mixin<T extends object, S extends object, R extends object = T>(dest: T, source: S, template: { [p in keyof S]?: keyof R; }): T & R;
174 169 export function mixin<T extends object, S extends object>(dest: T, source: S, template?: any): any {
175 170 argumentNotNull(dest, "dest");
176 171 const _res: any = dest as any;
177 172
178 173 if (isPrimitive(source))
179 174 return _res;
180 175
181 176 if (template instanceof Array) {
182 177 template.forEach(p => {
183 178 if (isKeyof(p, source))
184 179 _res[p] = source[p];
185 180 });
186 181 } else if (template) {
187 182 keys(source).forEach(p => {
188 183 if (isKeyof(p, template))
189 184 _res[template[p]] = source[p];
190 185 });
191 186 } else {
192 187 keys(source).forEach(p => _res[p] = source[p]);
193 188 }
194 189
195 190 return _res;
196 191 }
197 192
198 193 /** Wraps the specified function to emulate an asynchronous execution.
199 194 * @param{Object} thisArg [Optional] Object which will be passed as 'this' to the function.
200 195 * @param{Function|String} fn [Required] Function wich will be wrapped.
201 196 */
202 197 export function async<T, F extends (...args: any[]) => T | PromiseLike<T>>(
203 198 fn: F,
204 199 thisArg?: ThisParameterType<F>
205 200 ): (...args: Parameters<F>) => PromiseLike<T>;
206 201 export function async<T, M extends string, O extends { [m in M]?: (...args: any[]) => T | PromiseLike<T> }>(
207 202 fn: M,
208 203 thisArg: O
209 204 ): (...args: Parameters<NonNullable<O[M]>>) => PromiseLike<T>;
210 205 export function async(_fn: any, thisArg: any): (...args: any[]) => PromiseLike<any> {
211 206 let fn = _fn;
212 207
213 208 if (arguments.length === 2 && !(fn instanceof Function))
214 209 fn = thisArg[fn];
215 210
216 211 if (fn == null)
217 212 throw new Error("The function must be specified");
218 213
219 214 function wrapresult(x: any, e?: any): PromiseLike<any> {
220 215 if (e) {
221 216 return {
222 217 then(cb, eb) {
223 218 try {
224 219 return eb ? wrapresult(eb(e)) : this;
225 220 } catch (e2) {
226 221 return wrapresult(null, e2);
227 222 }
228 223 }
229 224 };
230 225 } else {
231 226 if (x && x.then)
232 227 return x;
233 228 return {
234 229 then(cb) {
235 230 try {
236 231 return cb ? wrapresult(cb(x)) : this;
237 232 } catch (e2) {
238 233 return wrapresult(e2);
239 234 }
240 235 }
241 236 };
242 237 }
243 238 }
244 239
245 240 return (...args) => {
246 241 try {
247 242 return wrapresult(fn.apply(thisArg, args));
248 243 } catch (e) {
249 244 return wrapresult(null, e);
250 245 }
251 246 };
252 247 }
253 248
254 249 export function delegate<T extends object, F extends (this: T, ...args: any[]) => any>(
255 250 target: T,
256 251 method: F
257 252 ): OmitThisParameter<F>;
258 253 export function delegate<M extends string, T extends { [m in M]?: (...args: any[]) => any; }>(
259 254 target: T,
260 255 method: M
261 256 ): OmitThisParameter<T[M]>;
262 257 export function delegate(target: any, _method: any): (...args: any[]) => any {
263 258 let method: any;
264 259 if (!(_method instanceof Function)) {
265 260 argumentNotNull(target, "target");
266 261 method = target[_method];
267 262 if (!(method instanceof Function))
268 263 throw new Error("'method' argument must be a Function or a method name");
269 264 } else {
270 265 method = _method;
271 266 }
272 267
273 268 return (...args) => {
274 269 return method.apply(target, args);
275 270 };
276 271 }
277 272
278 273 export function delay(timeMs: number, ct = Cancellation.none) {
279 274 ct.throwIfRequested();
280 275 return new Promise((resolve, reject) => {
281 276 const h = ct.register(e => {
282 277 clearTimeout(id);
283 278 reject(e);
284 279 // we don't nedd to unregister h, since ct is already disposed
285 280 });
286 281 const id = setTimeout(() => {
287 282 h.destroy();
288 283 resolve();
289 284 }, timeMs);
290 285
291 286 });
292 287 }
293 288
294 289 /** Returns resolved promise, awaiting this method will cause the asynchronous
295 290 * completion of the rest of the code.
296 291 */
297 292 export function fork() {
298 293 return Promise.resolve();
299 294 }
300 295
301 296 /** Always throws Error, can be used as a stub for the methods which should be
302 297 * assigned later and are required to be not null.
303 298 */
304 299 export function notImplemented(): never {
305 300 throw new Error("Not implemeted");
306 301 }
307 302 /**
308 303 * Iterates over the specified array of items and calls the callback `cb`, if
309 304 * the result of the callback is a promise the next item from the array will be
310 305 * proceeded after the promise is resolved.
311 306 *
312 307 */
313 308 export function pmap<T, T2>(
314 309 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
315 310 cb: (item: T, i: number) => T2 | PromiseLike<T2>
316 311 ): T2[] | PromiseLike<T2[]> {
317 312 argumentNotNull(cb, "cb");
318 313
319 314 if (isPromise(items)) {
320 315 return items.then(data => pmap(data, cb));
321 316 } else {
322 317
323 318 if (isNull(items) || !items.length)
324 319 return [];
325 320
326 321 let i = 0;
327 322 const result = new Array<T2>();
328 323
329 324 const next = (): any => {
330 325 while (i < items.length) {
331 326 const r = cb(items[i], i);
332 327 const ri = i;
333 328 i++;
334 329 if (isPromise(r)) {
335 330 return r.then(x => {
336 331 result[ri] = x;
337 332 return next();
338 333 });
339 334 } else {
340 335 result[ri] = r;
341 336 }
342 337 }
343 338 return result;
344 339 };
345 340
346 341 return next();
347 342 }
348 343 }
349 344
350 345 export function pfor<T>(
351 346 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
352 347 cb: (item: T, i: number) => any
353 348 ): void | PromiseLike<void> {
354 349 argumentNotNull(cb, "cb");
355 350
356 351 if (isPromise(items)) {
357 352 return items.then(data => pfor(data, cb));
358 353 } else {
359 354 if (isNull(items) || !items.length)
360 355 return;
361 356
362 357 let i = 0;
363 358
364 359 const next = (): any => {
365 360 while (i < items.length) {
366 361 const r = cb(items[i], i);
367 362 i++;
368 363 if (isPromise(r))
369 364 return r.then(next);
370 365 }
371 366 };
372 367
373 368 return next();
374 369 }
375 370 }
376 371
377 372 export function first<T>(sequence: ArrayLike<T>): T;
378 373 export function first<T>(sequence: PromiseLike<ArrayLike<T>>): PromiseLike<T>;
379 374 export function first<T>(
380 375 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
381 376 cb?: (x: T) => void,
382 377 err?: (x: Error) => void
383 378 ): void;
384 379 /**
385 380 * Выбирает первый элемент из последовательности, или обещания, если в
386 381 * качестве параметра используется обещание, оно должно вернуть массив.
387 382 *
388 383 * @param {Function} cb обработчик результата, ему будет передан первый
389 384 * элемент последовательности в случае успеха
390 385 * @param {Function} err обработчик исключения, если массив пустой, либо
391 386 * не массив
392 387 *
393 388 * @remarks Если не указаны ни cb ни err, тогда функция вернет либо
394 389 * обещание, либо первый элемент.
395 390 * @async
396 391 */
397 392 export function first<T>(
398 393 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
399 394 cb?: (x: T) => void,
400 395 err?: (x: Error) => void
401 396 ) {
402 397 if (isPromise(sequence)) {
403 398 return sequence.then(res => first(res, cb as any /* force to pass undefined cb */, err));
404 399 } else if (sequence && "length" in sequence) {
405 400 if (sequence.length === 0) {
406 401 if (err)
407 402 return err(new Error("The sequence is empty"));
408 403 else
409 404 throw new Error("The sequence is empty");
410 405 } else if (cb) {
411 406 return cb(sequence[0]);
412 407 } else {
413 408 return sequence[0];
414 409 }
415 410 } else {
416 411 if (err)
417 412 return err(new Error("The sequence is required"));
418 413 else
419 414 throw new Error("The sequence is required");
420 415 }
421 416 }
422 417
423 418 export function firstWhere<T>(
424 419 sequence: ArrayLike<T>,
425 420 predicate: (x: T) => boolean
426 421 ): T;
427 422 export function firstWhere<T>(
428 423 sequence: PromiseLike<ArrayLike<T>>,
429 424 predicate: (x: T) => boolean
430 425 ): PromiseLike<T>;
431 426 export function firstWhere<T>(
432 427 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
433 428 predicate: (x: T) => boolean,
434 429 cb: (x: T) => void,
435 430 err?: (x: Error) => void
436 431 ): void;
437 432
438 433 export function firstWhere<T>(
439 434 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
440 435 predicate?: (x: T) => boolean,
441 436 cb?: (x: T) => any,
442 437 err?: (x: Error) => any
443 438 ) {
444 439 if (isPromise(sequence)) {
445 440 return sequence.then(res => firstWhere(
446 441 res,
447 442 predicate as any /* force to pass undefined predicate */,
448 443 cb as any /* force to pass undefined cb */,
449 444 err)
450 445 );
451 446 } else if (sequence && "length" in sequence) {
452 447 if (sequence.length === 0) {
453 448 if (err)
454 449 err(new Error("The sequence is empty"));
455 450 else
456 451 throw new Error("The sequence is empty");
457 452 } else {
458 453 if (!predicate) {
459 454 return cb ? cb(sequence[0]) && void (0) : sequence[0];
460 455 } else {
461 456 for (let i = 0; i < sequence.length; i++) {
462 457 const v = sequence[i];
463 458 if (predicate(v))
464 459 return cb ? cb(v) : v;
465 460 }
466 461 if (err)
467 462 err(new Error("The sequence doesn't contain matching items"));
468 463 else
469 464 throw new Error("The sequence doesn't contain matching items");
470 465 }
471 466 }
472 467 } else {
473 468 if (err)
474 469 err(new Error("The sequence is required"));
475 470 else
476 471 throw new Error("The sequence is required");
477 472 }
478 473 }
479 474
480 475 export function isDestroyable(d: any): d is IDestroyable {
481 476 if (d && "destroy" in d && typeof(destroy) === "function")
482 477 return true;
483 478 return false;
484 479 }
485 480
486 481 export function destroy(d: any) {
487 482 if (d && "destroy" in d)
488 483 d.destroy();
489 484 }
490 485
491 486 /**
492 487 * Used to mark that the async operation isn't awaited intentionally.
493 488 * @param p The promise which represents the async operation.
494 489 */
495 490 export function nowait(p: Promise<any>) {
496 491 }
497 492
498 493 /** represents already destroyed object.
499 494 */
500 495 export const destroyed = {
501 496 /** Calling to this method doesn't affect anything, noop.
502 497 */
503 498 destroy() {
504 499 }
505 500 };
@@ -1,178 +1,178
1 import { isPrimitive, isNull, each, isKeyof, get } from "../safe";
1 import { isPrimitive, isNull, isKeyof, get } from "../safe";
2 2 import { MapOf } from "../interfaces";
3 3
4 4 type SubstFn = (name: string, format?: string) => string;
5 5 type TemplateFn = (subst: SubstFn) => string | undefined;
6 6 type ConvertFn = (value: any, format?: string) => string;
7 7
8 8 const map = {
9 9 "\\{": "&curlopen;",
10 10 "\\}": "&curlclose;",
11 11 "&": "&amp;",
12 12 "\\:": "&colon;"
13 13 };
14 14
15 15 const rev = {
16 16 curlopen: "{",
17 17 curlclose: "}",
18 18 amp: "&",
19 19 colon: ":"
20 20 };
21 21
22 22 function espaceString(s: string) {
23 23 if (!s)
24 24 return s;
25 25 return "'" + s.replace(/('|\\)/g, "\\$1").replace("\n", "\\n") + "'";
26 26 }
27 27
28 28 function encode(s: string) {
29 29 if (!s)
30 30 return s;
31 31 return s.replace(/\\{|\\}|&|\\:|\n/g, m => isKeyof(m, map) ? map[m] : m);
32 32 }
33 33
34 34 function decode(s: string) {
35 35 if (!s)
36 36 return s;
37 37 return s.replace(/&(\w+);/g, (m, $1) => isKeyof($1, rev) ? rev[$1] : m);
38 38 }
39 39
40 40 function subst(s: string) {
41 41 const i = s.indexOf(":");
42 42 let name: string;
43 43 let pattern: string | undefined;
44 44 if (i >= 0) {
45 45 name = s.substr(0, i);
46 46 pattern = s.substr(i + 1);
47 47 } else {
48 48 name = s;
49 49 }
50 50
51 51 if (pattern)
52 52 return [
53 53 espaceString(decode(name)),
54 54 espaceString(decode(pattern))
55 55 ];
56 56 else
57 57 return [espaceString(decode(name))];
58 58 }
59 59
60 60 function _compile(str: string) {
61 61 if (!str)
62 62 return () => void 0;
63 63
64 64 const chunks = encode(str).split("{");
65 65 let chunk: string;
66 66
67 67 const code = ["var result=[];"];
68 68
69 69 for (let i = 0; i < chunks.length; i++) {
70 70 chunk = chunks[i];
71 71
72 72 if (i === 0) {
73 73 if (chunk)
74 74 code.push("result.push(" + espaceString(decode(chunk)) +
75 75 ");");
76 76 } else {
77 77 const len = chunk.indexOf("}");
78 78 if (len < 0)
79 79 throw new Error("Unbalanced substitution #" + i);
80 80
81 81 code.push("result.push(subst(" +
82 82 subst(chunk.substr(0, len)).join(",") + "));");
83 83 if (chunk.length > len + 1)
84 84 code.push("result.push(" +
85 85 espaceString(decode(chunk.substr(len + 1))) + ");");
86 86 }
87 87 }
88 88
89 89 code.push("return result.join('');");
90 90
91 91 // the code for this function is generated from the template
92 92 // tslint:disable-next-line:function-constructor
93 93 return new Function("subst", code.join("\n")) as TemplateFn;
94 94 }
95 95
96 96 const cache: MapOf<TemplateFn> = {};
97 97
98 98 export function compile(template: string) {
99 99 let compiled = cache[template];
100 100 if (!compiled) {
101 101 compiled = _compile(template);
102 102 cache[template] = compiled;
103 103 }
104 104 return compiled;
105 105 }
106 106
107 107 function defaultConverter(value: any, pattern?: string) {
108 108 if (pattern && pattern.toLocaleLowerCase() === "json") {
109 109 const seen: any = [];
110 110 return JSON.stringify(value, (k, v) => {
111 111 if (!isPrimitive(v)) {
112 112 const id = seen.indexOf(v);
113 113 if (id >= 0)
114 114 return "@ref-" + id;
115 115 else {
116 116 seen.push(v);
117 117 return v.toString() as string;
118 118 }
119 119 } else {
120 120 return isNull(v) ? "" : v.toString();
121 121 }
122 122 }, 2);
123 123 } else if (isNull(value)) {
124 124 return "";
125 125 } else if (value instanceof Date) {
126 126 return value.toISOString();
127 127 } else {
128 128 return value.toString() as string;
129 129 }
130 130 }
131 131
132 132 export class Formatter {
133 133 _converters: ConvertFn[];
134 134
135 135 constructor(converters?: ConvertFn[]) {
136 136 this._converters = converters || [];
137 137 this._converters.push(defaultConverter);
138 138 }
139 139
140 140 convert(value: any, pattern?: string) {
141 141 for (const c of this._converters) {
142 142 const res = c(value, pattern);
143 143 if (!isNull(res))
144 144 return res;
145 145 }
146 146 return "";
147 147 }
148 148
149 149 format(msg: string, ...args: any[]) {
150 150 const template = compile(msg);
151 151
152 152 return template((name, pattern) => {
153 153 const value = get(name, args);
154 154 return !isNull(value) ? this.convert(value, pattern) : "";
155 155 });
156 156
157 157 }
158 158
159 159 compile(msg: string) {
160 160 const template = compile(msg);
161 161 return (...args: any[]) => {
162 162 return template((name, pattern) => {
163 163 const value = get(name, args);
164 164 return !isNull(value) ? this.convert(value, pattern) : "";
165 165 });
166 166 };
167 167 }
168 168 }
169 169
170 170 const _default = new Formatter();
171 171
172 172 export function format(msg: string, ...args: any[]) {
173 173 return _default.format(msg, ...args);
174 174 }
175 175
176 176 export function convert(value: any, pattern: string) {
177 177 return _default.format(value, pattern);
178 178 }
@@ -1,39 +1,43
1 1 import { Foo } from "./Foo";
2 import { annotate, dependency } from "./services";
3 2
4 export const service = annotate<Bar>();
3 /* export const service = annotate<Bar>();
5 4
6 5 @service.wire({
7 6 foo: dependency("foo"),
8 7 nested: {
9 8 lazy: dependency("foo", { lazy: true })
10 9 },
11 10 host: dependency("host")
12 }, "")
11 }, "") */
13 12 export class Bar {
14 13 barName = "Twister";
15 14
16 15 _v: Foo | undefined;
17 16
18 constructor(_opts: {
17 constructor(
18 _opts: {
19 19 foo?: Foo;
20 20 nested?: {
21 21 lazy: () => Foo
22 22 },
23 23 host: string
24 }, s: string) {
24 },
25 s: string
26 ) {
25 27
26 28 if (_opts && _opts.foo)
27 29 this._v = _opts.foo;
30 if (s)
31 this.barName = s;
28 32 }
29 33
30 34 setName(name: string) {
31 35
32 36 }
33 37
34 38 getFoo() {
35 39 if (this._v === undefined)
36 40 throw new Error("The foo isn't set");
37 41 return this._v;
38 42 }
39 43 }
@@ -1,29 +1,28
1 1 import { Bar } from "./Bar";
2 import { annotate, dependency } from "./services";
3 2
4 3 // export service descriptor
5 4 // через service передается информация о типе зависимости
6 5 // даже если это шаблон.
7 export const service = annotate<Box<Bar>>();
6 // export const service = annotate<Box<Bar>>();
8 7
9 @service.wire()
8 // @service.wire()
10 9 export class Box<T> {
11 10 private _value: T | undefined;
12 11
13 12 constructor(value?: T) {
14 13 this._value = value;
15 14 }
16 15
17 @service.inject(dependency("bar"))
16 // @service.inject(dependency("bar"))
18 17 setValue(value: T) {
19 18 this._value = value;
20 19 return value;
21 20 }
22 21
23 22 getValue() {
24 23 if (this._value === undefined)
25 24 throw new Error("Trying to get a value from the empty box");
26 25
27 26 return this._value;
28 27 }
29 28 }
@@ -1,20 +1,23
1 import { configure } from "./services";
1 import { Services } from "./services";
2 import { configure } from "../di/traits";
3 import { LifetimeManager } from "../di/LifetimeManager";
2 4
3 export const config = configure()
5 export const config = configure<Services>()
4 6 .register("host", s => s.value("example.com"))
5 7 .register("bar2", bar2 => Promise.all([import("./Foo"), import("./Bar")])
6 8 .then(([{ Foo }, { Bar }]) => {
7 const lifetime: any = undefined; // new HierarchyLifetime()
9 const lifetime = LifetimeManager.hierarchyLifetime();
10
8 11 bar2.factory((resolve, activate) => {
9 12 const bar = new Bar({
10 13 foo: activate(lifetime, () => new Foo()),
11 14 nested: {
12 15 lazy: resolve("foo", { lazy: true })
13 16 },
14 17 host: resolve("host")
15 18 }, "some text");
16 19 bar.setName(resolve("host"));
17 20 return bar;
18 21 });
19 22 })
20 23 );
@@ -1,25 +1,19
1 1 import { Foo } from "./Foo";
2 2 import { Bar } from "./Bar";
3 3 import { Box } from "./Box";
4 import { declare } from "../di/traits";
5 4
6 5 /**
7 6 * Сервисы доступные внутри контейнера
8 7 */
9 8 export interface Services {
10 9 foo: Foo;
11 10
12 11 bar: Bar;
13 12
14 13 bar2: Bar;
15 14
16 15 box: Box<Bar>;
17 16
18 17 host: string;
19 18
20 19 }
21
22 /**
23 * Экспортируем вспомогательные функции для описания сервисов и кинфогурации
24 */
25 export const { dependency, annotate, configure } = declare<Services>();
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now