##// END OF EJS Templates
working on IoC configuration
cin -
r114:475b8ce3e850 ioc ts support
parent child
Show More
@@ -1,132 +1,138
1 1 import { TraceSource } from "../log/TraceSource";
2 2 import { argumentNotNull, argumentNotEmptyString, isPrimitive, each, isNull } from "../safe";
3 3 import { Descriptor, ServiceMap } from "./interfaces";
4 4 import { Container } from "./Container";
5 import { MapOf } from "../interfaces";
5 6
6 7 const trace = TraceSource.get("@implab/core/di/ActivationContext");
7 8
8 export interface ActivationContextInfo {
9 export interface ActivationContextInfo<S> {
9 10 name: string;
10 11
11 12 service: string;
12 13
13 scope: ServiceMap;
14 scope: ServiceMap<S>;
14 15 }
15 16
16 export class ActivationContext {
17 _cache: object;
17 export class ActivationContext<S> {
18 _cache: MapOf<any>;
18 19
19 _services: ServiceMap;
20 _services: ServiceMap<S>;
20 21
21 _stack: ActivationContextInfo[];
22 _stack: ActivationContextInfo<S>[];
22 23
23 _visited: object;
24 _visited: MapOf<any>;
24 25
25 26 _name: string;
26 27
27 _localized: boolean;
28 _localized: boolean = false;
28 29
29 container: Container;
30 container: Container<S>;
30 31
31 constructor(container: Container, services: ServiceMap, name?: string, cache?: object, visited?) {
32 constructor(container: Container<S>, services: ServiceMap<S>, name?: string, cache?: object, visited?: MapOf<any>) {
32 33 argumentNotNull(container, "container");
33 34 argumentNotNull(services, "services");
34 35
35 this._name = name;
36 this._name = name || "<unnamed>";
36 37 this._visited = visited || {};
37 38 this._stack = [];
38 39 this._cache = cache || {};
39 40 this._services = services;
40 41 this.container = container;
41 42 }
42 43
43 44 getName() {
44 45 return this._name;
45 46 }
46 47
47 resolve(name, def?): any {
48 resolve<K extends keyof S, T extends S[K]>(name: K, def?: T) {
48 49 const d = this._services[name];
49 50
50 if (!d)
51 if (arguments.length > 1)
51 if (d !== undefined) {
52 return this.activate(d as Descriptor<T>, name.toString());
53 } else {
54 if (def !== undefined && def !== null)
52 55 return def;
53 56 else
54 57 throw new Error(`Service ${name} not found`);
55
56 return this.activate(d, name);
58 }
57 59 }
58 60
59 61 /**
60 62 * registers services local to the the activation context
61 63 *
62 64 * @name{string} the name of the service
63 65 * @service{string} the service descriptor to register
64 66 */
65 register(name: string, service: Descriptor) {
67 register<K extends keyof S>(name: K, service: Descriptor<S[K]>) {
66 68 argumentNotEmptyString(name, "name");
67 69
68 70 this._services[name] = service;
69 71 }
70 72
71 73 clone() {
72 74 return new ActivationContext(
73 75 this.container,
74 76 this._services,
75 77 this._name,
76 78 this._cache,
77 79 this._visited
78 80 );
79 81 }
80 82
81 83 has(id: string) {
82 84 return id in this._cache;
83 85 }
84 86
85 get(id: string) {
87 get<T>(id: string) {
86 88 return this._cache[id];
87 89 }
88 90
89 store(id: string, value) {
91 store(id: string, value: any) {
90 92 return (this._cache[id] = value);
91 93 }
92 94
93 activate(d: Descriptor, name: string) {
95 activate<T>(d: Descriptor<T>, name: string) {
94 96 if (trace.isLogEnabled())
95 97 trace.log(`enter ${name} ${d}`);
96 98
97 99 this.enter(name, d.toString());
98 const v = d.activate(this);
100 const v = d.activate<S>(this);
99 101 this.leave();
100 102
101 103 if (trace.isLogEnabled())
102 104 trace.log(`leave ${name}`);
103 105
104 106 return v;
105 107 }
106 108
107 109 visit(id: string) {
108 110 const count = this._visited[id] || 0;
109 111 this._visited[id] = count + 1;
110 112 return count;
111 113 }
112 114
113 115 getStack() {
114 116 return this._stack.slice().reverse();
115 117 }
116 118
117 119 private enter(name: string, service: string) {
118 120 this._stack.push({
119 121 name,
120 122 service,
121 123 scope: this._services
122 124 });
123 125 this._name = name;
124 126 this._services = Object.create(this._services);
125 127 }
126 128
127 129 private leave() {
128 130 const ctx = this._stack.pop();
129 this._services = ctx.scope;
130 this._name = ctx.name;
131 if (ctx) {
132 this._services = ctx.scope;
133 this._name = ctx.name;
134 } else {
135 trace.error("Trying to leave the last activation scope");
136 }
131 137 }
132 138 }
@@ -1,36 +1,39
1 import { ActivationContextInfo } from "./ActivationContext";
1 export interface ActivationItem {
2 name: string;
3 service: string;
4 }
2 5
3 6 export class ActivationError {
4 activationStack: ActivationContextInfo[];
7 activationStack: ActivationItem[];
5 8
6 9 service: string;
7 10
8 11 innerException: any;
9 12
10 13 message: string;
11 14
12 constructor(service: string, activationStack: ActivationContextInfo[], innerException: any) {
15 constructor(service: string, activationStack: ActivationItem[], innerException: any) {
13 16 this.message = "Failed to activate the service";
14 17 this.activationStack = activationStack;
15 18 this.service = service;
16 19 this.innerException = innerException;
17 20 }
18 21
19 22 toString() {
20 23 const parts = [this.message];
21 24 if (this.service)
22 25 parts.push("when activating: " + this.service.toString());
23 26
24 27 if (this.innerException)
25 28 parts.push("caused by: " + this.innerException.toString());
26 29
27 30 if (this.activationStack) {
28 31 parts.push("at");
29 32 this.activationStack
30 33 .forEach(x => parts.push(` ${x.name} ${x.service}`));
31 34
32 35 }
33 36
34 37 return parts.join("\n");
35 38 }
36 39 }
@@ -1,37 +1,39
1 1 import { Descriptor, isDescriptor } from "./interfaces";
2 2 import { ActivationContext } from "./ActivationContext";
3 3 import { isPrimitive } from "../safe";
4 4
5 export class AggregateDescriptor<T> implements Descriptor<T> {
5 type Parse<T> = T extends Descriptor<infer V> ? V :
6 T extends {} ? { [K in keyof T]: Parse<T[K]> } :
7 T;
8
9 export class AggregateDescriptor<T> implements Descriptor<Parse<T>> {
6 10 _value: T;
7 11
8 12 constructor(value: T) {
9 13 this._value = value;
10 14 }
11 15
12 activate(context: ActivationContext) {
16 activate<S>(context: ActivationContext<S>) {
13 17 return this._parse(this._value, context, "$value");
14 18 }
15 19
16 // TODO: make async
17 _parse(value: T, context: ActivationContext, path: string) {
20 _parse<S, V>(value: V, context: ActivationContext<S>, path: string): Parse<V> {
18 21 if (isPrimitive(value))
19 return value;
22 return value as any;
20 23
21 24 if (isDescriptor(value))
22 25 return context.activate(value, path);
23 26
24 27 if (value instanceof Array)
25 return value.map((x, i) => this._parse(x, context, `${path}[${i}]`));
28 return value.map((x, i) => this._parse(x, context, `${path}[${i}]`)) as any;
26 29
27 const t = {};
28 for (const p of Object.keys(value))
30 const t: any = {};
31 for (const p in value)
29 32 t[p] = this._parse(value[p], context, `${path}.${p}`);
30 33 return t;
31
32 34 }
33 35
34 36 toString() {
35 37 return "@walk";
36 38 }
37 39 }
@@ -1,41 +1,61
1 1 import { Constructor } from "../interfaces";
2 2
3 3 export interface InjectOptions {
4 4 lazy?: boolean;
5 5 }
6 6
7 interface Dependency<K extends keyof any> {
8 $dependency: K;
9
10 lazy?: boolean;
11 }
12
13 interface Lazy<K extends keyof any> extends Dependency<K> {
14 lazy: true;
15 }
16
7 17 type Setter<T = any> = (v: T) => void;
8 18
9 19 type Compatible<T1, T2> = T1 extends T2 ? any : never;
10 20
11 type SetterType<T> = T extends (v: infer V) => void ? V : never;
12
13 21 type ExtractService<K, S> = K extends keyof S ? S[K] : K;
14 22
15 23 type ExtractDependency<D, S> = D extends { $dependency: infer K } ? D extends { lazy: true } ? () => ExtractService<K, S> : ExtractService<K, S> : VisitDependency<D, S>;
16 24
17 25 type VisitDependency<D, S> = D extends {} ? { [K in keyof D]: ExtractDependency<D[K], S> } : D;
18 26
27 interface Config<S> {
28 dependency<K extends keyof S>(name: K): Dependency<K>;
29
30 lazy<K extends keyof S>(name: K): Lazy<K>;
31
32 build<T>(): Builder<T, S>;
33 }
34
35 export declare function services<S extends object>(): Config<S>;
36
19 37 export class Builder<T, S> {
20 38 consume<P extends any[]>(...args: P) {
21 39 return <C extends new (...args: ExtractDependency<P, S>) => T>(constructor: C) => {
22 return constructor as typeof constructor & { service: () => T };
40 return constructor;
23 41 };
24 42 }
25 43
26 44 inject<K extends keyof S>(dependency: K) {
27 45 // K = "bar"
28 46 // M = "setValue"
29 47 // S[K] = Bar
30 48 // T[M] = (value: string) => void
31 49 // P[m] = (value: V) => void
32 50 return <P, M extends keyof (T | P)>(target: P, memberName: M, descriptor: TypedPropertyDescriptor<Compatible<T[M], Setter<S[K]>>>) => {
33 51
34 52 };
35 53 }
36 54
37 55 cast<T2 extends T>(): Builder<T2, S> {
38 56 return this as Builder<T2, S>;
39 57 }
40 58
41 59 }
60
61
@@ -1,12 +1,12
1 1 export class ConfigError extends Error {
2 inner: any;
2 inner?: {};
3 3
4 path: string;
4 path?: string;
5 5
6 configName: string;
6 configName?: string;
7 7
8 constructor(message: string, inner?: any) {
8 constructor(message: string, inner?: {}) {
9 9 super(message);
10 10 this.inner = inner;
11 11 }
12 12 }
@@ -1,350 +1,348
1 1 import {
2 2 ServiceRegistration,
3 3 TypeRegistration,
4 4 FactoryRegistration,
5 5 ServiceMap,
6 6 isDescriptor,
7 7 isDependencyRegistration,
8 8 DependencyRegistration,
9 9 ValueRegistration,
10 10 ActivationType,
11 11 isValueRegistration,
12 12 isTypeRegistration,
13 13 isFactoryRegistration
14 14 } from "./interfaces";
15 15
16 16 import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get } from "../safe";
17 17 import { AggregateDescriptor } from "./AggregateDescriptor";
18 18 import { ValueDescriptor } from "./ValueDescriptor";
19 19 import { Container } from "./Container";
20 20 import { ReferenceDescriptor } from "./ReferenceDescriptor";
21 21 import { TypeServiceDescriptor } from "./TypeServiceDescriptor";
22 22 import { FactoryServiceDescriptor } from "./FactoryServiceDescriptor";
23 23 import { TraceSource } from "../log/TraceSource";
24 24 import { ConfigError } from "./ConfigError";
25 25 import { Cancellation } from "../Cancellation";
26 26 import { makeResolver } from "./ResolverHelper";
27 27 import { ICancellation } from "../interfaces";
28 28
29 29 const trace = TraceSource.get("@implab/core/di/Configuration");
30 30
31 async function mapAll(data: any | any[], map?: (v: any, k: keyof any) => any): Promise<any> {
31 async function mapAll(data: any | any[], map?: (v: any, k: number | string) => any): Promise<any> {
32 32 if (data instanceof Array) {
33 33 return Promise.all(map ? data.map(map) : data);
34 34 } else {
35 35 const keys = Object.keys(data);
36 36
37 37 const o: any = {};
38 38
39 39 await Promise.all(keys.map(async k => {
40 40 const v = map ? map(data[k], k) : data[k];
41 41 o[k] = isPromise(v) ? await v : v;
42 42 }));
43 43
44 44 return o;
45 45 }
46 46 }
47 47
48 48 export type ModuleResolver = (moduleName: string, ct?: ICancellation) => any;
49 49
50 type _key = string | number;
51
52 export class Configuration {
50 export class Configuration<S> {
53 51
54 52 _hasInnerDescriptors = false;
55 53
56 _container: Container;
54 _container: Container<S>;
57 55
58 _path: Array<_key>;
56 _path: Array<string>;
59 57
60 58 _configName: string | undefined;
61 59
62 60 _require: ModuleResolver | undefined;
63 61
64 constructor(container: Container) {
62 constructor(container: Container<S>) {
65 63 argumentNotNull(container, "container");
66 64 this._container = container;
67 65 this._path = [];
68 66 }
69 67
70 68 async loadConfiguration(moduleName: string, contextRequire?: any, ct = Cancellation.none) {
71 69 argumentNotEmptyString(moduleName, "moduleName");
72 70
73 71 trace.log(
74 72 "loadConfiguration moduleName={0}, contextRequire={1}",
75 73 moduleName,
76 74 contextRequire ? typeof (contextRequire) : "<nil>"
77 75 );
78 76
79 77 this._configName = moduleName;
80 78
81 79 const r = await makeResolver(undefined, contextRequire);
82 80
83 81 const config = await r(moduleName, ct);
84 82
85 83 await this._applyConfiguration(
86 84 config,
87 85 await makeResolver(moduleName, contextRequire),
88 86 ct
89 87 );
90 88 }
91 89
92 90 async applyConfiguration(data: object, contextRequire?: any, ct = Cancellation.none) {
93 91 argumentNotNull(data, "data");
94 92
95 93 await this._applyConfiguration(data, await makeResolver(void (0), contextRequire), ct);
96 94 }
97 95
98 96 async _applyConfiguration(data: object, resolver?: ModuleResolver, ct = Cancellation.none) {
99 97 trace.log("applyConfiguration");
100 98
101 99 this._configName = "$";
102 100
103 101 if (resolver)
104 102 this._require = resolver;
105 103
106 104 let services: ServiceMap;
107 105
108 106 try {
109 107 services = await this._visitRegistrations(data, "$");
110 108 } catch (e) {
111 109 throw this._makeError(e);
112 110 }
113 111
114 112 this._container.register(services);
115 113 }
116 114
117 115 _makeError(inner: any) {
118 116 const e = new ConfigError("Failed to load configuration", inner);
119 117 e.configName = this._configName || "<inline>";
120 118 e.path = this._makePath();
121 119 return e;
122 120 }
123 121
124 122 _makePath() {
125 123 return this._path
126 124 .reduce(
127 125 (prev, cur) => typeof cur === "number" ?
128 126 `${prev}[${cur}]` :
129 127 `${prev}.${cur}`
130 128 )
131 129 .toString();
132 130 }
133 131
134 132 async _resolveType(moduleName: string, localName: string) {
135 133 trace.log("resolveType moduleName={0}, localName={1}", moduleName, localName);
136 134 try {
137 135 const m = await this._loadModule(moduleName);
138 136 return localName ? get(localName, m) : m;
139 137 } catch (e) {
140 138 trace.error("Failed to resolve type moduleName={0}, localName={1}", moduleName, localName);
141 139 throw e;
142 140 }
143 141 }
144 142
145 143 _loadModule(moduleName: string) {
146 144 trace.debug("loadModule {0}", moduleName);
147 145 if (!this._require)
148 146 throw new Error("Module loader isn't specified");
149 147
150 148 return this._require(moduleName);
151 149 }
152 150
153 async _visitRegistrations(data: any, name: _key) {
151 async _visitRegistrations(data: any, name: string) {
154 152 this._enter(name);
155 153
156 154 if (data.constructor &&
157 155 data.constructor.prototype !== Object.prototype)
158 156 throw new Error("Configuration must be a simple object");
159 157
160 158 const o: ServiceMap = {};
161 159 const keys = Object.keys(data);
162 160
163 161 const services = await mapAll(data, async (v, k) => {
164 const d = await this._visit(v, k);
162 const d = await this._visit(v, k.toString());
165 163 return isDescriptor(d) ? d : new AggregateDescriptor(d);
166 164 }) as ServiceMap;
167 165
168 166 this._leave();
169 167
170 168 return services;
171 169 }
172 170
173 171 _enter(name: keyof any) {
174 172 this._path.push(name.toString());
175 173 trace.debug(">{0}", name);
176 174 }
177 175
178 176 _leave() {
179 177 const name = this._path.pop();
180 178 trace.debug("<{0}", name);
181 179 }
182 180
183 async _visit<T extends object>(data: T, name: keyof T) {
181 async _visit<T>(data: T, name: string) {
184 182 if (isPrimitive(data) || isDescriptor(data))
185 183 return data;
186 184
187 if (isDependencyRegistration(data)) {
185 if (isDependencyRegistration<S>(data)) {
188 186 return this._visitDependencyRegistration(data, name);
189 187 } else if (isValueRegistration(data)) {
190 188 return this._visitValueRegistration(data, name);
191 189 } else if (isTypeRegistration(data)) {
192 190 return this._visitTypeRegistration(data, name);
193 191 } else if (isFactoryRegistration(data)) {
194 192 return this._visitFactoryRegistration(data, name);
195 193 } else if (data instanceof Array) {
196 194 return this._visitArray(data, name);
197 195 }
198 196
199 return this._visitObject(data, name);
197 return this._visitObject(data as T & object, name);
200 198 }
201 199
202 async _visitObject(data: object, name: _key) {
200 async _visitObject<T extends object>(data: T, name: string) {
203 201 if (data.constructor &&
204 202 data.constructor.prototype !== Object.prototype)
205 203 return new ValueDescriptor(data);
206 204
207 205 this._enter(name);
208 206
209 207 const v = await mapAll(data, delegate(this, "_visit"));
210 208
211 209 // TODO: handle inline descriptors properly
212 210 // const ex = {
213 211 // activate(ctx) {
214 212 // const value = ctx.activate(this.prop, "prop");
215 213 // // some code
216 214 // },
217 215 // // will be turned to ReferenceDescriptor
218 216 // prop: { $dependency: "depName" }
219 217 // };
220 218
221 219 this._leave();
222 220 return v;
223 221 }
224 222
225 async _visitArray(data: any[], name: _key) {
223 async _visitArray(data: any[], name: string) {
226 224 if (data.constructor &&
227 225 data.constructor.prototype !== Array.prototype)
228 226 return new ValueDescriptor(data);
229 227
230 228 this._enter(name);
231 229
232 230 const v = await mapAll(data, delegate(this, "_visit"));
233 231 this._leave();
234 232
235 233 return v;
236 234 }
237 235
238 _makeServiceParams<T, P, S>(data: ServiceRegistration<T, P, S>) {
236 _makeServiceParams<T, P>(data: ServiceRegistration<T, P, S>) {
239 237 const opts: any = {
240 238 owner: this._container
241 239 };
242 240 if (data.services)
243 241 opts.services = this._visitRegistrations(data.services, "services");
244 242
245 243 if (data.inject) {
246 244 this._enter("inject");
247 245 opts.inject = mapAll(
248 246 data.inject instanceof Array ?
249 247 data.inject :
250 248 [data.inject],
251 249 delegate(this, "_visitObject")
252 250 );
253 251 this._leave();
254 252 }
255 253
256 254 if ("params" in data)
257 255 opts.params = data.params instanceof Array ?
258 256 this._visitArray(data.params, "params") :
259 257 this._visit(data.params, "params");
260 258
261 259 if (data.activation) {
262 260 if (typeof (data.activation) === "string") {
263 261 switch (data.activation.toLowerCase()) {
264 262 case "singleton":
265 263 opts.activation = ActivationType.Singleton;
266 264 break;
267 265 case "container":
268 266 opts.activation = ActivationType.Container;
269 267 break;
270 268 case "hierarchy":
271 269 opts.activation = ActivationType.Hierarchy;
272 270 break;
273 271 case "context":
274 272 opts.activation = ActivationType.Context;
275 273 break;
276 274 case "call":
277 275 opts.activation = ActivationType.Call;
278 276 break;
279 277 default:
280 278 throw new Error("Unknown activation type: " +
281 279 data.activation);
282 280 }
283 281 } else {
284 282 opts.activation = Number(data.activation);
285 283 }
286 284 }
287 285
288 286 if (data.cleanup)
289 287 opts.cleanup = data.cleanup;
290 288
291 289 return opts;
292 290 }
293 291
294 async _visitValueRegistration<T>(data: ValueRegistration<T>, name: _key) {
292 async _visitValueRegistration<T>(data: ValueRegistration<T>, name: string) {
295 293 this._enter(name);
296 294 const d = data.parse ? new AggregateDescriptor(data.$value) : new ValueDescriptor(data.$value);
297 295 this._leave();
298 296 return d;
299 297 }
300 298
301 async _visitDependencyRegistration<S, K extends keyof S>(data: DependencyRegistration<S, K>, name: keyof S) {
299 async _visitDependencyRegistration<K extends keyof S>(data: DependencyRegistration<S, K>, name: string) {
302 300 argumentNotEmptyString(data && data.$dependency, "data.$dependency");
303 301 this._enter(name);
304 302 const d = new ReferenceDescriptor<S, K>({
305 303 name: data.$dependency,
306 304 lazy: data.lazy,
307 305 optional: data.optional,
308 306 default: data.default,
309 307 services: data.services && await this._visitRegistrations(data.services, "services")
310 308 });
311 309 this._leave();
312 310 return d;
313 311 }
314 312
315 async _visitTypeRegistration<T, P, S>(data: TypeRegistration<T, P, S>, name: _key) {
313 async _visitTypeRegistration<T, P>(data: TypeRegistration<T, P, S>, name: string) {
316 314 argumentNotNull(data.$type, "data.$type");
317 315 this._enter(name);
318 316
319 317 const opts = this._makeServiceParams(data);
320 318 if (data.$type instanceof Function) {
321 319 opts.type = data.$type;
322 320 } else {
323 321 const [moduleName, typeName] = data.$type.split(":", 2);
324 322 opts.type = this._resolveType(moduleName, typeName);
325 323 }
326 324
327 325 const d = new TypeServiceDescriptor(
328 326 await mapAll(opts)
329 327 );
330 328
331 329 this._leave();
332 330
333 331 return d;
334 332 }
335 333
336 async _visitFactoryRegistration<T, P, S>(data: FactoryRegistration<T, P, S>, name: _key) {
334 async _visitFactoryRegistration<T, P>(data: FactoryRegistration<T, P, S>, name: string) {
337 335 argumentOfType(data.$factory, Function, "data.$factory");
338 336 this._enter(name);
339 337
340 338 const opts = this._makeServiceParams(data);
341 339 opts.factory = data.$factory;
342 340
343 341 const d = new FactoryServiceDescriptor(
344 342 await mapAll(opts)
345 343 );
346 344
347 345 this._leave();
348 346 return d;
349 347 }
350 348 }
@@ -1,128 +1,138
1 1 import { ActivationContext } from "./ActivationContext";
2 2 import { ValueDescriptor } from "./ValueDescriptor";
3 3 import { ActivationError } from "./ActivationError";
4 import { isDescriptor, ServiceMap } from "./interfaces";
4 import { isDescriptor, ServiceMap, Descriptor } from "./interfaces";
5 5 import { TraceSource } from "../log/TraceSource";
6 6 import { Configuration } from "./Configuration";
7 7 import { Cancellation } from "../Cancellation";
8 import { MapOf } from "../interfaces";
8 9
9 10 const trace = TraceSource.get("@implab/core/di/ActivationContext");
10 11
11 export class Container {
12 _services: ServiceMap;
12 export class Container<S extends { container?: Container<S> }> {
13 readonly _services: ServiceMap<S>;
13 14
14 _cache: object;
15 readonly _cache: MapOf<any>;
16
17 readonly _cleanup: (() => void)[];
15 18
16 _cleanup: (() => void)[];
19 readonly _root: Container<S>;
17 20
18 _root: Container;
21 readonly _parent?: Container<S>;
19 22
20 _parent: Container;
23 _disposed: boolean;
21 24
22 constructor(parent?: Container) {
25 constructor(parent?: Container<S>) {
23 26 this._parent = parent;
24 27 this._services = parent ? Object.create(parent._services) : {};
25 28 this._cache = {};
26 29 this._cleanup = [];
27 30 this._root = parent ? parent.getRootContainer() : this;
28 31 this._services.container = new ValueDescriptor(this);
32 this._disposed = false;
29 33 }
30 34
31 35 getRootContainer() {
32 36 return this._root;
33 37 }
34 38
35 39 getParent() {
36 40 return this._parent;
37 41 }
38 42
39 resolve(name: string, def?) {
43 resolve<K extends keyof S, T extends S[K] = S[K]>(name: K, def?: T) {
40 44 trace.debug("resolve {0}", name);
41 45 const d = this._services[name];
42 46 if (d === undefined) {
43 if (arguments.length > 1)
47 if (def !== undefined)
44 48 return def;
45 49 else
46 50 throw new Error("Service '" + name + "' isn't found");
47 }
51 } else {
48 52
49 const context = new ActivationContext(this, this._services);
50 try {
51 return context.activate(d, name);
52 } catch (error) {
53 throw new ActivationError(name, context.getStack(), error);
53 const context = new ActivationContext<S>(this, this._services);
54 try {
55 return context.activate(d as Descriptor<T>, name.toString());
56 } catch (error) {
57 throw new ActivationError(name.toString(), context.getStack(), error);
58 }
54 59 }
55 60 }
56 61
57 62 /**
58 63 * @deprecated use resolve() method
59 64 */
60 getService() {
61 return this.resolve.apply(this, arguments);
65 getService<K extends keyof S>(name: K, def?: S[K]) {
66 return this.resolve(name, def);
62 67 }
63 68
64 register(nameOrCollection, service?) {
69 register<K extends keyof S>(name: K, service: Descriptor<S[K]>): this;
70 register(services: ServiceMap<S>): this;
71 register<K extends keyof S>(nameOrCollection: K | ServiceMap<S>, service?: Descriptor<S[K]>) {
65 72 if (arguments.length === 1) {
66 const data = nameOrCollection;
67 for (const name in data)
68 this.register(name, data[name]);
73 const data = nameOrCollection as ServiceMap<S>;
74 for (const name in data) {
75 if (Object.prototype.hasOwnProperty.call(data, name)) {
76 this.register(name, data[name] as Descriptor<S[keyof S]>);
77 }
78 }
69 79 } else {
70 80 if (!isDescriptor(service))
71 81 throw new Error("The service parameter must be a descriptor");
72 82
73 this._services[nameOrCollection] = service;
83 this._services[nameOrCollection as K] = service;
74 84 }
75 85 return this;
76 86 }
77 87
78 onDispose(callback) {
88 onDispose(callback: () => void) {
79 89 if (!(callback instanceof Function))
80 90 throw new Error("The callback must be a function");
81 91 this._cleanup.push(callback);
82 92 }
83 93
84 94 dispose() {
85 if (this._cleanup) {
86 for (const f of this._cleanup)
87 f();
88 this._cleanup = null;
89 }
95 if (this._disposed)
96 return;
97 this._disposed = true;
98 for (const f of this._cleanup)
99 f();
90 100 }
91 101
92 102 /**
93 103 * @param{String|Object} config
94 104 * The configuration of the contaier. Can be either a string or an object,
95 105 * if the configuration is an object it's treated as a collection of
96 106 * services which will be registed in the contaier.
97 107 *
98 108 * @param{Function} opts.contextRequire
99 109 * The function which will be used to load a configuration or types for services.
100 110 *
101 111 */
102 112 async configure(config: string | object, opts?: any, ct = Cancellation.none) {
103 113 const c = new Configuration(this);
104 114
105 115 if (typeof (config) === "string") {
106 116 return c.loadConfiguration(config, opts && opts.contextRequire, ct);
107 117 } else {
108 118 return c.applyConfiguration(config, opts && opts.contextRequire, ct);
109 119 }
110 120 }
111 121
112 createChildContainer() {
113 return new Container(this);
122 createChildContainer<S2 extends { container?: Container<S & S2> } = S>(): Container<S & S2> {
123 return new Container<S & S2>(this as any);
114 124 }
115 125
116 126 has(id: string | number) {
117 127 return id in this._cache;
118 128 }
119 129
120 130 get(id: string | number) {
121 131 return this._cache[id];
122 132 }
123 133
124 134 store(id: string | number, value: any) {
125 135 return (this._cache[id] = value);
126 136 }
127 137
128 138 }
@@ -1,101 +1,99
1 import { isNull, argumentNotEmptyString, each } from "../safe";
1 import { isNull, argumentNotEmptyString, each, keys } from "../safe";
2 2 import { ActivationContext } from "./ActivationContext";
3 3 import { ServiceMap, Descriptor } from "./interfaces";
4 4 import { ActivationError } from "./ActivationError";
5 5
6 6 export interface ReferenceDescriptorParams<S, K extends keyof S> {
7 7 name: K;
8 8 lazy?: boolean;
9 9 optional?: boolean;
10 10 default?: S[K];
11 11 services?: ServiceMap;
12 12 }
13 13
14 function def<T>(v: T) {
15 if (v === undefined)
16 throw Error();
17 return v;
18 }
19
14 20 export class ReferenceDescriptor<S, K extends keyof S> implements Descriptor<S[K]> {
15 21 _name: K;
16 22
17 23 _lazy = false;
18 24
19 25 _optional = false;
20 26
21 27 _default: any;
22 28
23 _services: ServiceMap;
29 _services: ServiceMap<S>;
24 30
25 31 constructor(opts: ReferenceDescriptorParams<S, K>) {
26 32 argumentNotEmptyString(opts && opts.name, "opts.name");
27 33 this._name = opts.name;
28 34 this._lazy = !!opts.lazy;
29 35 this._optional = !!opts.optional;
30 36 this._default = opts.default;
31 37
32 38 this._services = opts.services || {};
33 39 }
34 40
35 activate(context: ActivationContext, name: string) {
41 activate(context: ActivationContext<S>) {
36 42 // добавляем сервисы
37 43 if (this._services) {
38 for (const p of Object.keys(this._services))
39 context.register(p, this._services[p]);
44 each(this._services, (v, k) => context.register(k, def(v)));
40 45 }
41 46
42 47 if (this._lazy) {
43 48 const saved = context.clone();
44 49
45 return (cfg: ServiceMap) => {
50 return (cfg: Partial<ServiceMap<S>>) => {
46 51 // защищаем контекст на случай исключения в процессе
47 52 // активации
48 53 const ct = saved.clone();
49 54 try {
50 55 if (cfg) {
51 for (const k in cfg)
52 ct.register(k, cfg[k]);
56 each(cfg, (v, k) => ct.register(k, v || {}));
53 57 }
54 58
55 59 return this._optional ? ct.resolve(this._name, this._default) : ct
56 60 .resolve(this._name);
57 61 } catch (error) {
58 62 throw new ActivationError(this._name.toString(), ct.getStack(), error);
59 63 }
60 64 };
61 65 } else {
62 // добавляем сервисы
63 if (this._services) {
64 for (const p of Object.keys(this._services))
65 context.register(p, this._services[p]);
66 }
67
68 66 const v = this._optional ?
69 67 context.resolve(this._name, this._default) :
70 68 context.resolve(this._name);
71 69
72 70 return v;
73 71 }
74 72 }
75 73
76 74 toString() {
77 75 const opts = [];
78 76 if (this._optional)
79 77 opts.push("optional");
80 78 if (this._lazy)
81 79 opts.push("lazy");
82 80
83 81 const parts = [
84 82 "@ref "
85 83 ];
86 84 if (opts.length) {
87 85 parts.push("{");
88 86 parts.push(opts.join());
89 87 parts.push("} ");
90 88 }
91 89
92 90 parts.push(this._name.toString());
93 91
94 92 if (!isNull(this._default)) {
95 93 parts.push(" = ");
96 94 parts.push(this._default);
97 95 }
98 96
99 97 return parts.join("");
100 98 }
101 99 }
@@ -1,75 +1,75
1 1 import { isPrimitive } from "../safe";
2 2 import { ActivationContext } from "./ActivationContext";
3 3 import { Constructor, Factory } from "../interfaces";
4 4
5 5 export interface Descriptor<T = any> {
6 activate(context: ActivationContext, name?: string): T;
6 activate<S>(context: ActivationContext<S>): T;
7 7 }
8 8
9 9 export function isDescriptor(x: any): x is Descriptor {
10 10 return (!isPrimitive(x)) &&
11 11 (x.activate instanceof Function);
12 12 }
13 13
14 14 export type ServiceMap<S = any> = {
15 15 [k in keyof S]: Descriptor<S[k]>;
16 16 }
17 17
18 18 export enum ActivationType {
19 19 Singleton = 1,
20 20 Container,
21 21 Hierarchy,
22 22 Context,
23 23 Call
24 24 }
25 25
26 26 export interface RegistrationWithServices<S> {
27 27 services?: ServiceMap<S>;
28 28 }
29 29
30 30 export interface ServiceRegistration<T, P, S> extends RegistrationWithServices<S> {
31 31
32 32 activation?: "singleton" | "container" | "hierarchy" | "context" | "call";
33 33
34 34 params?: P;
35 35
36 36 inject?: object | object[];
37 37
38 38 cleanup?: ((instance: T) => void) | string;
39 39 }
40 40
41 41 export interface TypeRegistration<T, P, S> extends ServiceRegistration<T, P, S> {
42 42 $type: string | Constructor<T>;
43 43 }
44 44
45 45 export interface FactoryRegistration<T, P, S> extends ServiceRegistration<T, P, S> {
46 46 $factory: string | Factory<T>;
47 47 }
48 48
49 49 export interface ValueRegistration<T> {
50 50 $value: T;
51 51 parse?: boolean;
52 52 }
53 53
54 54 export interface DependencyRegistration<S, K extends keyof S> extends RegistrationWithServices<S> {
55 55 $dependency: K;
56 56 lazy?: boolean;
57 57 optional?: boolean;
58 58 default?: S[K];
59 59 }
60 60
61 61 export function isTypeRegistration(x: any): x is TypeRegistration<any, any, any> {
62 62 return (!isPrimitive(x)) && ("$type" in x);
63 63 }
64 64
65 65 export function isFactoryRegistration(x: any): x is FactoryRegistration<any, any, any> {
66 66 return (!isPrimitive(x)) && ("$factory" in x);
67 67 }
68 68
69 69 export function isValueRegistration(x: any): x is ValueRegistration<any> {
70 70 return (!isPrimitive(x)) && ("$value" in x);
71 71 }
72 72
73 export function isDependencyRegistration(x: any): x is DependencyRegistration<any, string | number | symbol> {
73 export function isDependencyRegistration<S>(x: any): x is DependencyRegistration<S, keyof S> {
74 74 return (!isPrimitive(x)) && ("$dependency" in x);
75 75 }
@@ -1,452 +1,461
1 1 import { ICancellable, Constructor } 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 export function oid(instance: object): string {
9 export function oid(instance: any): string | undefined {
10 10 if (isNull(instance))
11 return null;
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 export function keys<T>(arg: T): (Extract<keyof T, string>)[] {
20 return isObject(arg) && arg ? Object.keys(arg) as (Extract<keyof T, string>)[] : [];
21 }
22
19 23 export function argumentNotNull(arg: any, name: string) {
20 24 if (arg === null || arg === undefined)
21 25 throw new Error("The argument " + name + " can't be null or undefined");
22 26 }
23 27
24 28 export function argumentNotEmptyString(arg: any, name: string) {
25 29 if (typeof (arg) !== "string" || !arg.length)
26 30 throw new Error("The argument '" + name + "' must be a not empty string");
27 31 }
28 32
29 33 export function argumentNotEmptyArray(arg: any, name: string) {
30 34 if (!(arg instanceof Array) || !arg.length)
31 35 throw new Error("The argument '" + name + "' must be a not empty array");
32 36 }
33 37
34 38 export function argumentOfType(arg: any, type: Constructor<{}>, name: string) {
35 39 if (!(arg instanceof type))
36 40 throw new Error("The argument '" + name + "' type doesn't match");
37 41 }
38 42
43 export function isObject(val: any): val is object {
44 return typeof val === "object";
45 }
46
39 47 export function isNull(val: any) {
40 48 return (val === null || val === undefined);
41 49 }
42 50
43 51 export function isPrimitive(val: any): val is string | number | boolean | undefined | null {
44 52 return (val === null || val === undefined || typeof (val) === "string" ||
45 53 typeof (val) === "number" || typeof (val) === "boolean");
46 54 }
47 55
48 56 export function isInteger(val: any): val is number {
49 57 return parseInt(val, 10) === val;
50 58 }
51 59
52 60 export function isNumber(val: any): val is number {
53 61 return parseFloat(val) === val;
54 62 }
55 63
56 64 export function isString(val: any): val is string {
57 65 return typeof (val) === "string" || val instanceof String;
58 66 }
59 67
60 68 export function isPromise(val: any): val is PromiseLike<any> {
61 69 return val && typeof val.then === "function";
62 70 }
63 71
64 72 export function isCancellable(val: any): val is ICancellable {
65 73 return val && typeof val.cancel === "function";
66 74 }
67 75
68 export function isNullOrEmptyString(val: any): val is string | null | undefined {
69 if (val === null || val === undefined ||
76 export function isNullOrEmptyString(val: any): val is (string | null | undefined) {
77 return (val === null || val === undefined ||
70 78 ((typeof (val) === "string" || val instanceof String) && val.length === 0))
71 return true;
72 79 }
73 80
74 81 export function isNotEmptyArray(arg: any): arg is Array<any> {
75 82 return (arg instanceof Array && arg.length > 0);
76 83 }
77 84
78 function _isStrictMode() {
85 function _isStrictMode(this: any) {
79 86 return !this;
80 87 }
81 88
82 function _getNonStrictGlobal() {
89 function _getNonStrictGlobal(this: any) {
83 90 return this;
84 91 }
85 92
86 93 export function getGlobal() {
87 94 // in es3 we can't use indirect call to eval, since it will
88 95 // be executed in the current call context.
89 96 if (!_isStrictMode()) {
90 97 return _getNonStrictGlobal();
91 98 } else {
92 99 // tslint:disable-next-line:no-eval
93 100 return eval.call(null, "this");
94 101 }
95 102 }
96 103
97 104 export function get(member: string, context?: object) {
98 105 argumentNotEmptyString(member, "member");
99 106 let that = context || getGlobal();
100 107 const parts = member.split(".");
101 108 for (const m of parts) {
102 109 if (!m)
103 110 continue;
104 111 if (isNull(that = that[m]))
105 112 break;
106 113 }
107 114 return that;
108 115 }
109 116
110 117 /**
111 118 * Выполняет метод для каждого элемента массива, останавливается, когда
112 119 * либо достигнут конец массива, либо функция <c>cb</c> вернула
113 120 * значение.
114 121 *
115 122 * @param {Array | Object} obj массив элементов для просмотра
116 123 * @param {Function} cb функция, вызываемая для каждого элемента
117 124 * @param {Object} thisArg значение, которое будет передано в качестве
118 125 * <c>this</c> в <c>cb</c>.
119 126 * @returns Результат вызова функции <c>cb</c>, либо <c>undefined</c>
120 127 * если достигнут конец массива.
121 128 */
122 export function each(obj, cb, thisArg?) {
129 export function each<T>(obj: T, cb: (v: T[keyof T], k: keyof T) => void): void;
130 export function each(obj: any, cb: any, thisArg?: any): any;
131 export function each(obj: any, cb: any, thisArg?: any) {
123 132 argumentNotNull(cb, "cb");
124 133 if (obj instanceof Array) {
125 134 for (let i = 0; i < obj.length; i++) {
126 135 const x = cb.call(thisArg, obj[i], i);
127 136 if (x !== undefined)
128 137 return x;
129 138 }
130 139 } else {
131 const keys = Object.keys(obj);
132 for (const k of keys) {
140 const _keys = Object.keys(obj);
141 for (const k of _keys) {
133 142 const x = cb.call(thisArg, obj[k], k);
134 143 if (x !== undefined)
135 144 return x;
136 145 }
137 146 }
138 147 }
139 148
140 149 /** Copies property values from a source object to the destination and returns
141 150 * the destination onject.
142 151 *
143 152 * @param dest The destination object into which properties from the source
144 153 * object will be copied.
145 154 * @param source The source of values which will be copied to the destination
146 155 * object.
147 156 * @param template An optional parameter specifies which properties should be
148 157 * copied from the source and how to map them to the destination. If the
149 158 * template is an array it contains the list of property names to copy from the
150 159 * source to the destination. In case of object the templates contains the map
151 160 * where keys are property names in the source and the values are property
152 161 * names in the destination object. If the template isn't specified then the
153 162 * own properties of the source are entirely copied to the destination.
154 163 *
155 164 */
156 165 export function mixin<T extends object, S extends object>(dest: T, source: S, template?: string[] | object): T & S {
157 166 argumentNotNull(dest, "to");
158 167 const _res = dest as T & S;
159 168
160 169 if (isPrimitive(source))
161 170 return _res;
162 171
163 172 if (template instanceof Array) {
164 173 for (const p of template) {
165 174 if (p in source)
166 175 _res[p] = source[p];
167 176 }
168 177 } else if (template) {
169 const keys = Object.keys(source);
170 for (const p of keys) {
178 const _keys = Object.keys(source);
179 for (const p of _keys) {
171 180 if (p in template)
172 181 _res[template[p]] = source[p];
173 182 }
174 183 } else {
175 184 const keys = Object.keys(source);
176 185 for (const p of keys)
177 186 _res[p] = source[p];
178 187 }
179 188
180 189 return _res;
181 190 }
182 191
183 192 /** Wraps the specified function to emulate an asynchronous execution.
184 193 * @param{Object} thisArg [Optional] Object which will be passed as 'this' to the function.
185 194 * @param{Function|String} fn [Required] Function wich will be wrapped.
186 195 */
187 export function async(_fn: (...args: any[]) => any, thisArg): (...args: any[]) => PromiseLike<any> {
196 export function async(_fn: (...args: any[]) => any, thisArg: any): (...args: any[]) => PromiseLike<any> {
188 197 let fn = _fn;
189 198
190 199 if (arguments.length === 2 && !(fn instanceof Function))
191 200 fn = thisArg[fn];
192 201
193 202 if (fn == null)
194 203 throw new Error("The function must be specified");
195 204
196 205 function wrapresult(x, e?): PromiseLike<any> {
197 206 if (e) {
198 207 return {
199 208 then(cb, eb) {
200 209 try {
201 210 return eb ? wrapresult(eb(e)) : this;
202 211 } catch (e2) {
203 212 return wrapresult(null, e2);
204 213 }
205 214 }
206 215 };
207 216 } else {
208 217 if (x && x.then)
209 218 return x;
210 219 return {
211 220 then(cb) {
212 221 try {
213 222 return cb ? wrapresult(cb(x)) : this;
214 223 } catch (e2) {
215 224 return wrapresult(e2);
216 225 }
217 226 }
218 227 };
219 228 }
220 229 }
221 230
222 231 return (...args) => {
223 232 try {
224 233 return wrapresult(fn.apply(thisArg, args));
225 234 } catch (e) {
226 235 return wrapresult(null, e);
227 236 }
228 237 };
229 238 }
230 239
231 240 type _AnyFn = (...args) => any;
232 241
233 242 export function delegate<T, K extends keyof T>(target: T, _method: (K | _AnyFn)) {
234 243 let method;
235 244 if (!(_method instanceof Function)) {
236 245 argumentNotNull(target, "target");
237 246 method = target[_method];
238 247 if (!(method instanceof Function))
239 248 throw new Error("'method' argument must be a Function or a method name");
240 249 } else {
241 250 method = _method;
242 251 }
243 252
244 253 return (...args) => {
245 254 return method.apply(target, args);
246 255 };
247 256 }
248 257
249 258 export function delay(timeMs: number, ct = Cancellation.none) {
250 259 ct.throwIfRequested();
251 260 return new Promise((resolve, reject) => {
252 261 const h = ct.register(e => {
253 262 clearTimeout(id);
254 263 reject(e);
255 264 // we don't nedd to unregister h, since ct is already disposed
256 265 });
257 266 const id = setTimeout(() => {
258 267 h.destroy();
259 268 resolve();
260 269 }, timeMs);
261 270
262 271 });
263 272 }
264 273
265 274 /**
266 275 * Iterates over the specified array of items and calls the callback `cb`, if
267 276 * the result of the callback is a promise the next item from the array will be
268 277 * proceeded after the promise is resolved.
269 278 *
270 279 */
271 280 export function pmap<T, T2>(
272 281 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
273 282 cb: (item: T, i: number) => T2 | PromiseLike<T2>
274 283 ): T2[] | PromiseLike<T2[]> {
275 284 argumentNotNull(cb, "cb");
276 285
277 286 if (isPromise(items)) {
278 287 return items.then(data => pmap(data, cb));
279 288 } else {
280 289
281 290 if (isNull(items) || !items.length)
282 291 return [];
283 292
284 293 let i = 0;
285 294 const result = new Array<T2>();
286 295
287 const next = () => {
296 const next = (): any => {
288 297 while (i < items.length) {
289 298 const r = cb(items[i], i);
290 299 const ri = i;
291 300 i++;
292 301 if (isPromise(r)) {
293 302 return r.then(x => {
294 303 result[ri] = x;
295 304 return next();
296 305 });
297 306 } else {
298 307 result[ri] = r;
299 308 }
300 309 }
301 310 return result;
302 311 };
303 312
304 313 return next();
305 314 }
306 315 }
307 316
308 317 export function pfor<T>(
309 318 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
310 319 cb: (item: T, i: number) => any
311 320 ): void | PromiseLike<void> {
312 321 argumentNotNull(cb, "cb");
313 322
314 323 if (isPromise(items)) {
315 324 return items.then(data => pfor(data, cb));
316 325 } else {
317 326 if (isNull(items) || !items.length)
318 327 return;
319 328
320 329 let i = 0;
321 330
322 const next = () => {
331 const next = (): any => {
323 332 while (i < items.length) {
324 333 const r = cb(items[i], i);
325 334 i++;
326 335 if (isPromise(r))
327 336 return r.then(next);
328 337 }
329 338 };
330 339
331 340 return next();
332 341 }
333 342 }
334 343
335 344 export function first<T>(sequence: ArrayLike<T>): T;
336 345 export function first<T>(sequence: PromiseLike<ArrayLike<T>>): PromiseLike<T>;
337 346 export function first<T>(
338 347 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
339 348 cb: (x: T) => void,
340 349 err?: (x: Error) => void
341 350 ): void;
342 351 /**
343 352 * Выбирает первый элемент из последовательности, или обещания, если в
344 353 * качестве параметра используется обещание, оно должно вернуть массив.
345 354 *
346 355 * @param {Function} cb обработчик результата, ему будет передан первый
347 356 * элемент последовательности в случае успеха
348 357 * @param {Function} err обработчик исключения, если массив пустой, либо
349 358 * не массив
350 359 *
351 360 * @remarks Если не указаны ни cb ни err, тогда функция вернет либо
352 361 * обещание, либо первый элемент.
353 362 * @async
354 363 */
355 364 export function first<T>(
356 365 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
357 366 cb?: (x: T) => void,
358 367 err?: (x: Error) => void
359 368 ) {
360 369 if (isPromise(sequence)) {
361 370 return sequence.then(res => first(res, cb, err));
362 371 } else if (sequence && "length" in sequence) {
363 372 if (sequence.length === 0) {
364 373 if (err)
365 374 return err(new Error("The sequence is empty"));
366 375 else
367 376 throw new Error("The sequence is empty");
368 377 } else if (cb) {
369 378 return cb(sequence[0]);
370 379 } else {
371 380 return sequence[0];
372 381 }
373 382 } else {
374 383 if (err)
375 384 return err(new Error("The sequence is required"));
376 385 else
377 386 throw new Error("The sequence is required");
378 387 }
379 388 }
380 389
381 390 export function firstWhere<T>(
382 391 sequence: ArrayLike<T>,
383 392 predicate: (x: T) => boolean
384 393 ): T;
385 394 export function firstWhere<T>(
386 395 sequence: PromiseLike<ArrayLike<T>>,
387 396 predicate: (x: T) => boolean
388 397 ): PromiseLike<T>;
389 398 export function firstWhere<T>(
390 399 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
391 400 predicate: (x: T) => boolean,
392 401 cb: (x: T) => void,
393 402 err?: (x: Error) => void
394 403 ): void;
395 404
396 405 export function firstWhere<T>(
397 406 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
398 407 predicate?: (x: T) => boolean,
399 408 cb?: (x: T) => any,
400 409 err?: (x: Error) => any
401 410 ) {
402 411 if (isPromise(sequence)) {
403 412 return sequence.then(res => firstWhere(res, predicate, cb, err));
404 413 } else if (sequence && "length" in sequence) {
405 414 if (sequence.length === 0) {
406 415 if (err)
407 416 err(new Error("The sequence is empty"));
408 417 else
409 418 throw new Error("The sequence is empty");
410 419 } else {
411 420 if (!predicate) {
412 421 return cb ? cb(sequence[0]) && void (0) : sequence[0];
413 422 } else {
414 423 for (let i = 0; i < sequence.length; i++) {
415 424 const v = sequence[i];
416 425 if (predicate(v))
417 426 return cb ? cb(v) : v;
418 427 }
419 428 if (err)
420 429 err(new Error("The sequence doesn't contain matching items"));
421 430 else
422 431 throw new Error("The sequence doesn't contain matching items");
423 432 }
424 433 }
425 434 } else {
426 435 if (err)
427 436 err(new Error("The sequence is required"));
428 437 else
429 438 throw new Error("The sequence is required");
430 439 }
431 440 }
432 441
433 442 export function destroy(d: any) {
434 443 if (d && "destroy" in d)
435 444 d.destroy();
436 445 }
437 446
438 447 /**
439 448 * Used to mark that the async operation isn't awaited intentionally.
440 449 * @param p The promise which represents the async operation.
441 450 */
442 451 export function nowait(p: Promise<any>) {
443 452 }
444 453
445 454 /** represents already destroyed object.
446 455 */
447 456 export const destroyed = {
448 457 /** Calling to this method doesn't affect anything, noop.
449 458 */
450 459 destroy() {
451 460 }
452 461 };
@@ -1,28 +1,35
1 import { config } from "./config";
1 import { services } from "../di/Annotations";
2 import { Bar } from "./Bar";
2 3
3 const service = config.build("barBox");
4 // declare required dependencies
5 const config = services<{
6 bar: Bar;
7 }>();
8
9 // export service descriptor
10 export const service = config.build<Box<Bar>>();
4 11
5 12 @service.consume(config.dependency("bar"))
6 13 export class Box<T> {
7 14 private _value: T | undefined;
8 15
9 16 constructor(value: T) {
10 17 this._value = value;
11 18 }
12 19
13 20 @service.inject("bar")
14 21 setValue(value: T) {
15 22 this._value = value;
16 23 }
17 24
18 25 setObj(value: object) {
19 26
20 27 }
21 28
22 29 getValue() {
23 30 if (this._value === undefined)
24 31 throw new Error("Trying to get a value from the empty box");
25 32
26 33 return this._value;
27 34 }
28 } No newline at end of file
35 }
General Comments 0
You need to be logged in to leave comments. Login now