##// END OF EJS Templates
fixes, tests...
cin -
r44:7a410676c874 di-typescript
parent child
Show More
@@ -0,0 +1,12
1 export class ConfigError extends Error {
2 inner;
3
4 path: string;
5
6 configName: string;
7
8 constructor(message: string, inner?) {
9 super(message);
10 this.inner = inner;
11 }
12 }
@@ -0,0 +1,23
1 import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor";
2 import { Factory } from "../interfaces";
3 import { argumentNotNull, oid } from "../safe";
4 import { ActivationType } from "./interfaces";
5
6 export interface FactoryServiceDescriptorParams extends ServiceDescriptorParams {
7 factory: Factory;
8 }
9
10 export class FactoryServiceDescriptor extends ServiceDescriptor {
11 constructor(opts: FactoryServiceDescriptorParams) {
12 super(opts);
13
14 argumentNotNull(opts && opts.factory, "opts.factory");
15
16 // bind to null
17 this._factory = () => opts.factory();
18
19 if (opts.activation === ActivationType.Singleton) {
20 this._cacheId = oid(opts.factory);
21 }
22 }
23 }
@@ -0,0 +1,36
1 import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor";
2 import { Constructor, Factory } from "../interfaces";
3 import { argumentNotNull, isPrimitive } from "../safe";
4
5 export interface TypeServiceDescriptorParams extends ServiceDescriptorParams {
6 type: Constructor;
7 }
8
9 export class TypeServiceDescriptor extends ServiceDescriptor {
10 _type: Constructor;
11
12 constructor(opts: TypeServiceDescriptorParams) {
13 super(opts);
14 argumentNotNull(opts && opts.type, "opts.type");
15
16 const ctor = this._type = opts.type;
17
18 if (this._params && this._params.length) {
19 this._factory = (...args) => {
20 const t = Object.create(ctor.prototype);
21 const inst = ctor.apply(t, args);
22 return isPrimitive(inst) ? t : inst;
23 };
24 } else {
25 this._factory = () => {
26 return new ctor();
27 };
28 }
29
30 }
31
32 toString() {
33 // @constructor {singleton} foo/bar/Baz
34 return ``;
35 }
36 }
@@ -1,2 +1,2
1 version=1.1.2
1 version=1.2.0
2 release=rtm No newline at end of file
2 release=rc No newline at end of file
@@ -1,37 +1,37
1 import { Descriptor, isDescriptor } from "./interfaces";
1 import { Descriptor, isDescriptor } from "./interfaces";
2 import { ActivationContext } from "./ActivationContext";
2 import { ActivationContext } from "./ActivationContext";
3 import { isPrimitive } from "util";
3 import { isPrimitive } from "../safe";
4
4
5 export class AggregateDescriptor implements Descriptor {
5 export class AggregateDescriptor implements Descriptor {
6 _value: object;
6 _value: object;
7
7
8 constructor(value: object) {
8 constructor(value: object) {
9 this._value = value;
9 this._value = value;
10 }
10 }
11
11
12 activate(context: ActivationContext) {
12 activate(context: ActivationContext) {
13 return this._parse(this._value, context, "$value");
13 return this._parse(this._value, context, "$value");
14 }
14 }
15
15
16 // TODO: make async
16 // TODO: make async
17 _parse(value, context: ActivationContext, path: string) {
17 _parse(value, context: ActivationContext, path: string) {
18 if (isPrimitive(value))
18 if (isPrimitive(value))
19 return value;
19 return value;
20
20
21 if (isDescriptor(value))
21 if (isDescriptor(value))
22 return context.activate(value, path);
22 return context.activate(value, path);
23
23
24 if (value instanceof Array)
24 if (value instanceof Array)
25 return value.map((x, i) => this._parse(x, context, `${path}[${i}]`));
25 return value.map((x, i) => this._parse(x, context, `${path}[${i}]`));
26
26
27 const t = {};
27 const t = {};
28 for (const p of Object.keys(value))
28 for (const p of Object.keys(value))
29 t[p] = this._parse(value[p], context, `${path}.${p}`);
29 t[p] = this._parse(value[p], context, `${path}.${p}`);
30 return t;
30 return t;
31
31
32 }
32 }
33
33
34 toString() {
34 toString() {
35 return "@walk";
35 return "@walk";
36 }
36 }
37 }
37 }
@@ -1,142 +1,353
1 import { ServiceRegistration, TypeRegistration, FactoryRegistration, ServiceMap, Descriptor, isDescriptor, isDependencyRegistration, DependencyRegistration, ValueRegistration, ActivationType, isValueRegistration, isTypeRegistration, isFactoryRegistration } from "./interfaces";
1 import {
2 import { isNullOrEmptyString, argumentNotEmptyString, isPrimitive } from "../safe";
2 ServiceRegistration,
3 TypeRegistration,
4 FactoryRegistration,
5 ServiceMap,
6 isDescriptor,
7 isDependencyRegistration,
8 DependencyRegistration,
9 ValueRegistration,
10 ActivationType,
11 isValueRegistration,
12 isTypeRegistration,
13 isFactoryRegistration
14 } from "./interfaces";
15
16 import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get } from "../safe";
3 import { AggregateDescriptor } from "./AggregateDescriptor";
17 import { AggregateDescriptor } from "./AggregateDescriptor";
4 import { ValueDescriptor } from "./ValueDescriptor";
18 import { ValueDescriptor } from "./ValueDescriptor";
5 import { ServiceDescriptorParams } from "./ServiceDescriptor";
6 import { Container } from "./Container";
19 import { Container } from "./Container";
7 import { Constructor } from "../interfaces";
20 import { ReferenceDescriptor } from "./ReferenceDescriptor";
21 import { TypeServiceDescriptor } from "./TypeServiceDescriptor";
22 import { FactoryServiceDescriptor } from "./FactoryServiceDescriptor";
23 import { rjs, createContextRequire, RequireFn } from "./RequireJsHelper";
24 import { TraceSource } from "../log/TraceSource";
25 import { ConfigError } from "./ConfigError";
26 import { Cancellation } from "../Cancellation";
27
28 const trace = TraceSource.get("@implab/core/di/Configuration");
29
30 async function mapAll(data: object | any[], map?: (v, k) => any): Promise<any> {
31 if (data instanceof Array) {
32 return Promise.all(map ? data.map(map) : data);
33 } else {
34 const keys = Object.keys(data);
35
36 const o: any = {};
37
38 await Promise.all(keys.map(async k => {
39 const v = map ? map(data[k], k) : data[k];
40 o[k] = isPromise(v) ? await v : v;
41 }));
42
43 return o;
44 }
45 }
8
46
9 interface MapOf<T> {
47 interface MapOf<T> {
10 [key: string]: T;
48 [key: string]: T;
11 }
49 }
12
50
13 type _key = string | number;
51 type _key = string | number;
14
52
15 export class Configuration {
53 export class Configuration {
16
54
17 _hasInnerDescriptors = false;
55 _hasInnerDescriptors = false;
18
56
19 _container: Container;
57 _container: Container;
20
58
21 _path: Array<_key>;
59 _path: Array<_key>;
22
60
61 _configName: string;
62
63 _require = rjs;
64
65 constructor(container: Container) {
66 argumentNotNull(container, container);
67 this._container = container;
68 this._path = [];
69 }
70
71 async loadConfiguration(moduleName: string, ct = Cancellation.none) {
72 argumentNotEmptyString(moduleName, "moduleName");
73
74 trace.log("loadConfiguration {0}", moduleName);
75
76 this._configName = moduleName;
77
78 const config = await this._loadModule(moduleName);
79
80 this._require = await this._createContextRequire(moduleName);
81
82 let services: ServiceMap;
83
84 try {
85 services = await this._visitRegistrations(config, moduleName);
86 } catch (e) {
87 throw this._makeError(e);
88 }
89
90 this._container.register(services);
91 }
92
93 async applyConfiguration(data: object, contextRequire?: RequireFn, ct = Cancellation.none) {
94 argumentNotNull(data, "data");
95
96 trace.log("applyConfiguration");
97
98 this._configName = "$";
99
100 if (contextRequire)
101 this._require = contextRequire;
102
103 let services: ServiceMap;
104
105 try {
106 services = await this._visitRegistrations(data, "$");
107 } catch (e) {
108 throw this._makeError(e);
109 }
110
111 this._container.register(services);
112 }
113
114 _makeError(inner) {
115 const e = new ConfigError("Failed to load configuration", inner);
116 e.configName = this._configName;
117 e.path = this._makePath();
118 return e;
119 }
120
121 _makePath() {
122 return this._path
123 .reduce(
124 (prev, cur) => typeof cur === "number" ?
125 `${prev}[${cur}]` :
126 `${prev}.${cur}`
127 )
128 .toString();
129 }
130
131 async _resolveType(moduleName: string, localName: string) {
132 trace.log("resolveType moduleName={0}, localName={1}", moduleName, localName);
133 try {
134 const m = await this._loadModule(moduleName);
135 return localName ? get(localName, m) : m;
136 } catch (e) {
137 trace.error("Failed to resolve type moduleName={0}, localName={1}", moduleName, localName);
138 throw e;
139 }
140 }
141
142 async _loadModule(moduleName: string) {
143 trace.log("loadModule {0}", moduleName);
144
145 const m = await new Promise(fulfill => {
146 this._require([moduleName], fulfill);
147 });
148
149 return m;
150 }
151
152 _createContextRequire(moduleName: string) {
153 return createContextRequire(moduleName);
154 }
155
23 async _visitRegistrations(data, name: _key) {
156 async _visitRegistrations(data, name: _key) {
24 this._path.push(name);
157 this._enter(name);
25
158
26 if (data.constructor &&
159 if (data.constructor &&
27 data.constructor.prototype !== Object.prototype)
160 data.constructor.prototype !== Object.prototype)
28 throw new Error("Configuration must be a simple object");
161 throw new Error("Configuration must be a simple object");
29
162
30 const o: ServiceMap = {};
163 const o: ServiceMap = {};
31 const keys = Object.keys(data);
164 const keys = Object.keys(data);
32
165
33 const res = await Promise.all(keys.map(k => this._visit(data[k], k)));
166 const services = await mapAll(data, async (v, k) => {
34 keys.forEach((k, i) => {
167 const d = await this._visit(v, k);
35 o[k] = isDescriptor(res[i]) ? res[i] : new AggregateDescriptor(res[i]);
168 return isDescriptor(d) ? d : new AggregateDescriptor(d);
36 });
169 }) as ServiceMap;
170
171 this._leave();
37
172
38 this._path.pop();
173 return services;
174 }
39
175
40 return o;
176 _enter(name: _key) {
177 this._path.push(name);
178 trace.debug(">{0}", name);
179 }
180
181 _leave() {
182 const name = this._path.pop();
183 trace.debug("<{0}", name);
41 }
184 }
42
185
43 async _visit(data, name: string): Promise<any> {
186 async _visit(data, name: string): Promise<any> {
44 if (isPrimitive(data) || isDescriptor(data))
187 if (isPrimitive(data) || isDescriptor(data))
45 return data;
188 return data;
46
189
47 if (isDependencyRegistration(data)) {
190 if (isDependencyRegistration(data)) {
48 return this._visitDependencyRegistration(data, name);
191 return this._visitDependencyRegistration(data, name);
49 } else if (isValueRegistration(data)) {
192 } else if (isValueRegistration(data)) {
50 return this._visitValueRegistration(data, name);
193 return this._visitValueRegistration(data, name);
51 } else if (isTypeRegistration(data)) {
194 } else if (isTypeRegistration(data)) {
52 return this._visitTypeRegistration(data, name);
195 return this._visitTypeRegistration(data, name);
53 } else if (isFactoryRegistration(data)) {
196 } else if (isFactoryRegistration(data)) {
54 return this._visitFactoryRegistration(data, name);
197 return this._visitFactoryRegistration(data, name);
55 } else if (data instanceof Array) {
198 } else if (data instanceof Array) {
56 return this._visitArray(data, name);
199 return this._visitArray(data, name);
57 }
200 }
58
201
59 return this._visitObject(data, name);
202 return this._visitObject(data, name);
60 }
203 }
61
204
62 async _resolveType(moduleName: string, typeName: string): Promise<Constructor> {
205 async _visitObject(data: object, name: _key) {
206 if (data.constructor &&
207 data.constructor.prototype !== Object.prototype)
208 return new ValueDescriptor(data);
63
209
64 }
210 this._enter(name);
211
212 const v = await mapAll(data, delegate(this, "_visit"));
65
213
66 async _visitObject(data: object, name: _key): Promise<object> {
214 // TODO: handle inline descriptors properly
67 this._path.push(name);
215 // const ex = {
68 this._path.pop();
216 // activate(ctx) {
69 }
217 // const value = ctx.activate(this.prop, "prop");
218 // // some code
219 // },
220 // // will be turned to ReferenceDescriptor
221 // prop: { $dependency: "depName" }
222 // };
70
223
71 async _visitArray(data: any[], name: _key): Promise<any[]> {
224 this._leave();
72 this._path.push(name);
225 return v;
73 this._path.pop();
74 }
226 }
75
227
76 async _makeServiceParams(data: ServiceRegistration) {
228 async _visitArray(data: any[], name: _key) {
77 const opts: any = {};
229 if (data.constructor &&
230 data.constructor.prototype !== Array.prototype)
231 return new ValueDescriptor(data);
232
233 this._enter(name);
234
235 const v = await mapAll(data, delegate(this, "_visit"));
236 this._leave();
237
238 return v;
239 }
240
241 _makeServiceParams(data: ServiceRegistration) {
242 const opts: any = {
243 owner: this._container
244 };
78 if (data.services)
245 if (data.services)
79 opts.services = await this._visitRegistrations(data.services, "services");
246 opts.services = this._visitRegistrations(data.services, "services");
80
247
81 if (data.inject) {
248 if (data.inject) {
82 if (data.inject instanceof Array) {
249 this._path.push("inject");
83 this._path.push("inject");
250 opts.inject = mapAll(
84 opts.inject = Promise.all(data.inject.map((x, i) => this._visitObject(x, i)));
251 data.inject instanceof Array ?
85 this._path.pop();
252 data.inject :
86 } else {
253 [data.inject],
87 opts.inject = [this._visitObject(data.inject, "inject")];
254 delegate(this, "_visitObject")
88 }
255 );
256 this._leave();
89 }
257 }
90
258
91 if (data.params)
259 if ("params" in data)
92 opts.params = await this._visit(data.params, "params");
260 opts.params = data.params instanceof Array ?
261 this._visitArray(data.params, "params") :
262 this._visit(data.params, "params");
93
263
94 if (data.activation) {
264 if (data.activation) {
95 if (typeof (data.activation) === "string") {
265 if (typeof (data.activation) === "string") {
96 switch (data.activation.toLowerCase()) {
266 switch (data.activation.toLowerCase()) {
97 case "singleton":
267 case "singleton":
98 opts.activation = ActivationType.Singleton;
268 opts.activation = ActivationType.Singleton;
99 break;
269 break;
100 case "container":
270 case "container":
101 opts.activation = ActivationType.Container;
271 opts.activation = ActivationType.Container;
102 break;
272 break;
103 case "hierarchy":
273 case "hierarchy":
104 opts.activation = ActivationType.Hierarchy;
274 opts.activation = ActivationType.Hierarchy;
105 break;
275 break;
106 case "context":
276 case "context":
107 opts.activation = ActivationType.Context;
277 opts.activation = ActivationType.Context;
108 break;
278 break;
109 case "call":
279 case "call":
110 opts.activation = ActivationType.Call;
280 opts.activation = ActivationType.Call;
111 break;
281 break;
112 default:
282 default:
113 throw new Error("Unknown activation type: " +
283 throw new Error("Unknown activation type: " +
114 data.activation);
284 data.activation);
115 }
285 }
116 } else {
286 } else {
117 opts.activation = Number(data.activation);
287 opts.activation = Number(data.activation);
118 }
288 }
119 }
289 }
120
290
121 if (data.cleanup)
291 if (data.cleanup)
122 opts.cleanup = data.cleanup;
292 opts.cleanup = data.cleanup;
293
294 return opts;
295 }
296
297 async _visitValueRegistration(data: ValueRegistration, name: _key) {
298 this._enter(name);
299 const d = data.parse ? new AggregateDescriptor(data.$value) : new ValueDescriptor(data.$value);
300 this._leave();
301 return d;
123 }
302 }
124
303
125 async _visitValueRegistration(item: ValueRegistration, name: _key) {
304 async _visitDependencyRegistration(data: DependencyRegistration, name: _key) {
126 this._path.push(name);
305 argumentNotEmptyString(data && data.$dependency, "data.$dependency");
127 this._path.pop();
306 this._enter(name);
307 const d = new ReferenceDescriptor({
308 name: data.$dependency,
309 lazy: data.lazy,
310 optional: data.optional,
311 default: data.default,
312 services: data.services && await this._visitRegistrations(data.services, "services")
313 });
314 this._leave();
315 return d;
128 }
316 }
129
317
130 async _visitDependencyRegistration(item: DependencyRegistration, name: _key) {
318 async _visitTypeRegistration(data: TypeRegistration, name: _key) {
131 this._path.push(name);
319 argumentNotNull(data.$type, "data.$type");
132 this._path.pop();
320 this._enter(name);
321
322 const opts = this._makeServiceParams(data);
323 if (data.$type instanceof Function) {
324 opts.type = data.$type;
325 } else {
326 const [moduleName, typeName] = data.$type.split(":", 2);
327 opts.type = this._resolveType(moduleName, typeName);
328 }
329
330 const d = new TypeServiceDescriptor(
331 await mapAll(opts)
332 );
333
334 this._leave();
335
336 return d;
133 }
337 }
134
338
135 async _visitTypeRegistration(item: TypeRegistration, name: _key) {
339 async _visitFactoryRegistration(data: FactoryRegistration, name: _key) {
136 argumentNotEmptyString(item.$type, "item.$type");
340 argumentOfType(data.$factory, Function, "data.$type");
137 }
341 this._enter(name);
342
343 const opts = this._makeServiceParams(data);
344 opts.factory = opts.$factory;
138
345
139 async _visitFactoryRegistration(item: FactoryRegistration, name: _key) {
346 const d = new FactoryServiceDescriptor(
140 argumentNotEmptyString(item.$factory, "item.$type");
347 await mapAll(opts)
348 );
349
350 this._leave();
351 return d;
141 }
352 }
142 }
353 }
@@ -1,291 +1,128
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2 import { ValueDescriptor } from "./ValueDescriptor";
2 import { ValueDescriptor } from "./ValueDescriptor";
3 import { ActivationError } from "./ActivationError";
3 import { ActivationError } from "./ActivationError";
4 import { isDescriptor, ActivationType, ServiceMap, isDependencyRegistration, isValueRegistration, ServiceRegistration, DependencyRegistration, ValueRegistration } from "./interfaces";
4 import { isDescriptor, ServiceMap } from "./interfaces";
5 import { AggregateDescriptor } from "./AggregateDescriptor";
6 import { isPrimitive, pmap } from "../safe";
7 import { ReferenceDescriptor } from "./ReferenceDescriptor";
8 import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor";
9 import { ModuleResolverBase } from "./ModuleResolverBase";
10 import format = require("../text/format");
11 import { TraceSource } from "../log/TraceSource";
5 import { TraceSource } from "../log/TraceSource";
12 import { RequireJsResolver } from "./RequireJsResolver";
6 import { Configuration } from "./Configuration";
7 import { Cancellation } from "../Cancellation";
13
8
14 const trace = TraceSource.get("@implab/core/di/ActivationContext");
9 const trace = TraceSource.get("@implab/core/di/ActivationContext");
15
10
16 export class Container {
11 export class Container {
17 _services: ServiceMap;
12 _services: ServiceMap;
18
13
19 _cache: object;
14 _cache: object;
20
15
21 _cleanup: (() => void)[];
16 _cleanup: (() => void)[];
22
17
23 _root: Container;
18 _root: Container;
24
19
25 _parent: Container;
20 _parent: Container;
26
21
27 _resolver: ModuleResolverBase;
28
29 constructor(parent?: Container) {
22 constructor(parent?: Container) {
30 this._parent = parent;
23 this._parent = parent;
31 this._services = parent ? Object.create(parent._services) : {};
24 this._services = parent ? Object.create(parent._services) : {};
32 this._cache = {};
25 this._cache = {};
33 this._cleanup = [];
26 this._cleanup = [];
34 this._root = parent ? parent.getRootContainer() : this;
27 this._root = parent ? parent.getRootContainer() : this;
35 this._services.container = new ValueDescriptor(this);
28 this._services.container = new ValueDescriptor(this);
36 this._resolver = new RequireJsResolver();
37 }
29 }
38
30
39 getRootContainer() {
31 getRootContainer() {
40 return this._root;
32 return this._root;
41 }
33 }
42
34
43 getParent() {
35 getParent() {
44 return this._parent;
36 return this._parent;
45 }
37 }
46
38
47 resolve(name: string, def?) {
39 resolve(name: string, def?) {
40 trace.debug("resolve {0}", name);
48 const d = this._services[name];
41 const d = this._services[name];
49 if (d === undefined) {
42 if (d === undefined) {
50 if (arguments.length > 1)
43 if (arguments.length > 1)
51 return def;
44 return def;
52 else
45 else
53 throw new Error("Service '" + name + "' isn't found");
46 throw new Error("Service '" + name + "' isn't found");
54 }
47 }
55
48
56 const context = new ActivationContext(this, this._services);
49 const context = new ActivationContext(this, this._services);
57 try {
50 try {
58 return context.activate(d, name);
51 return context.activate(d, name);
59 } catch (error) {
52 } catch (error) {
60 throw new ActivationError(name, context.getStack(), error);
53 throw new ActivationError(name, context.getStack(), error);
61 }
54 }
62 }
55 }
63
56
64 /**
57 /**
65 * @deprecated use resolve() method
58 * @deprecated use resolve() method
66 */
59 */
67 getService(name: string, def?) {
60 getService() {
68 return this.resolve.apply(this, arguments);
61 return this.resolve.apply(this, arguments);
69 }
62 }
70
63
71 register(nameOrCollection, service?) {
64 register(nameOrCollection, service?) {
72 if (arguments.length === 1) {
65 if (arguments.length === 1) {
73 const data = nameOrCollection;
66 const data = nameOrCollection;
74 for (const name in data)
67 for (const name in data)
75 this.register(name, data[name]);
68 this.register(name, data[name]);
76 } else {
69 } else {
77 if (!isDescriptor(service))
70 if (!isDescriptor(service))
78 throw new Error("The service parameter must be a descriptor");
71 throw new Error("The service parameter must be a descriptor");
79
72
80 this._services[nameOrCollection] = service;
73 this._services[nameOrCollection] = service;
81 }
74 }
82 return this;
75 return this;
83 }
76 }
84
77
85 onDispose(callback) {
78 onDispose(callback) {
86 if (!(callback instanceof Function))
79 if (!(callback instanceof Function))
87 throw new Error("The callback must be a function");
80 throw new Error("The callback must be a function");
88 this._cleanup.push(callback);
81 this._cleanup.push(callback);
89 }
82 }
90
83
91 dispose() {
84 dispose() {
92 if (this._cleanup) {
85 if (this._cleanup) {
93 for (const f of this._cleanup)
86 for (const f of this._cleanup)
94 f();
87 f();
95 this._cleanup = null;
88 this._cleanup = null;
96 }
89 }
97 }
90 }
98
91
99 /**
92 /**
100 * @param{String|Object} config
93 * @param{String|Object} config
101 * The configuration of the contaier. Can be either a string or an object,
94 * The configuration of the contaier. Can be either a string or an object,
102 * if the configuration is an object it's treated as a collection of
95 * if the configuration is an object it's treated as a collection of
103 * services which will be registed in the contaier.
96 * services which will be registed in the contaier.
104 *
97 *
105 * @param{Function} opts.contextRequire
98 * @param{Function} opts.contextRequire
106 * The function which will be used to load a configuration or types for services.
99 * The function which will be used to load a configuration or types for services.
107 *
100 *
108 */
101 */
109 async configure(config: string | object, opts?: object) {
102 async configure(config: string | object, opts?: any, ct = Cancellation.none) {
103 const c = new Configuration(this);
104
110 if (typeof (config) === "string") {
105 if (typeof (config) === "string") {
111 trace.log(`load configuration '${config}'`);
106 return c.loadConfiguration(config, ct);
112 const resolver = await this._resolver.createResolver(config, opts);
113 const data = await this._resolver.loadModule(config);
114 return this._configure(data, { resolver });
115 } else {
107 } else {
116 trace.log(`json configuration`);
108 return c.applyConfiguration(config, opts && opts.contextRequire, ct);
117 return this._configure(config);
118 }
109 }
119 }
110 }
120
111
121 createChildContainer() {
112 createChildContainer() {
122 return new Container(this);
113 return new Container(this);
123 }
114 }
124
115
125 has(id) {
116 has(id) {
126 return id in this._cache;
117 return id in this._cache;
127 }
118 }
128
119
129 get(id) {
120 get(id) {
130 return this._cache[id];
121 return this._cache[id];
131 }
122 }
132
123
133 store(id, value) {
124 store(id, value) {
134 return (this._cache[id] = value);
125 return (this._cache[id] = value);
135 }
126 }
136
127
137 async _configure(data: object, opts?: { resolver: ModuleResolverBase }) {
138 const resolver = (opts && opts.resolver) || this._resolver;
139
140 const services = await this._parseRegistrations(data, resolver);
141
142 this.register(services);
143 }
144
145 async _parse(data: any, resolver: ModuleResolverBase) {
146 if (isPrimitive(data) || isDescriptor(data))
147 return data;
148
149 if (isDependencyRegistration(data)) {
150 return this._makeReferenceDescriptor(data, resolver);
151 } else if (isValueRegistration(data)) {
152 return this._makeValueDescriptor(data, resolver);
153 } else if (data.$type || data.$factory) {
154 return this._makeServiceDescriptor(data, resolver);
155 } else if (data instanceof Array) {
156 return this._parseArray(data, resolver);
157 }
158
159 return this._parseObject(data, resolver);
160 }
161
162 async _makeValueDescriptor(data: ValueRegistration, resolver: ModuleResolverBase) {
163 return !data.parse ?
164 new ValueDescriptor(data.$value) :
165 new AggregateDescriptor(this._parse(data.$value, resolver));
166 }
167
168 async _makeReferenceDescriptor(registration: DependencyRegistration, resolver: ModuleResolverBase) {
169 return new ReferenceDescriptor({
170 name: registration.$dependency,
171 lazy: registration.lazy,
172 optional: registration.optional,
173 default: registration.default,
174 services: registration.services && await this._parseRegistrations(registration.services, resolver)
175 });
176 }
177
178 async _makeServiceDescriptor(data: ServiceRegistration, resolver: ModuleResolverBase) {
179 const opts: ServiceDescriptorParams = {
180 owner: this
181 };
182
183 if (data.$type) {
184 if (data.$type instanceof Function)
185 opts.type = data.$type;
186 else if (typeof data.$type === "string")
187 opts.type = await resolver.resolve(data.$type);
188 else
189 throw new Error(format("Unsupported type specification: {0:json}", data.$type));
190 } else {
191 if (data.$factory instanceof Function)
192 opts.factory = data.$factory;
193 else if (typeof data.$factory === "string")
194 opts.factory = await resolver.resolve(data.$factory);
195 else
196 throw new Error(format("Unsupported factory specification: {0:json}", data.$factory));
197 }
198
199 if (data.services)
200 opts.services = await this._parseRegistrations(data.services, resolver);
201
202 if (data.inject) {
203 if (data.inject instanceof Array)
204 opts.inject = await Promise.all(data.inject.map(x => this._parseObject(x, resolver)));
205 else
206 opts.inject = [await this._parseObject(data.inject, resolver)];
207 }
208
209 if (data.params)
210 opts.params = await this._parse(data.params, resolver);
211
212 if (data.activation) {
213 if (typeof (data.activation) === "string") {
214 switch (data.activation.toLowerCase()) {
215 case "singleton":
216 opts.activation = ActivationType.Singleton;
217 break;
218 case "container":
219 opts.activation = ActivationType.Container;
220 break;
221 case "hierarchy":
222 opts.activation = ActivationType.Hierarchy;
223 break;
224 case "context":
225 opts.activation = ActivationType.Context;
226 break;
227 case "call":
228 opts.activation = ActivationType.Call;
229 break;
230 default:
231 throw new Error("Unknown activation type: " +
232 data.activation);
233 }
234 } else {
235 opts.activation = Number(data.activation);
236 }
237 }
238
239 if (data.cleanup)
240 opts.cleanup = data.cleanup;
241
242 return new ServiceDescriptor(opts);
243 }
244
245 async _parseObject(data: object, resolver: ModuleResolverBase) {
246 if (data.constructor &&
247 data.constructor.prototype !== Object.prototype)
248 return new ValueDescriptor(data);
249
250 const o = {};
251
252 for (const p in data)
253 o[p] = await this._parse(data[p], resolver);
254
255 // TODO: handle inline descriptors properly
256 // const ex = {
257 // activate(ctx) {
258 // const value = ctx.activate(this.prop, "prop");
259 // // some code
260 // },
261
262 // // will be turned to ReferenceDescriptor
263 // prop: { $dependency: "depName" }
264 // };
265
266 return o;
267 }
268
269 async _parseArray(data: Array<any>, resolver: ModuleResolverBase) {
270 if (data.constructor &&
271 data.constructor.prototype !== Array.prototype)
272 return new ValueDescriptor(data);
273
274 return pmap(data, x => this._parse(x, resolver));
275 }
276
277 async _parseRegistrations(data: object, resolver: ModuleResolverBase) {
278 if (data.constructor &&
279 data.constructor.prototype !== Object.prototype)
280 throw new Error("Registrations must be a simple object");
281
282 const o: ServiceMap = {};
283
284 for (const p of Object.keys(data)) {
285 const v = await this._parse(data[p], resolver);
286 o[p] = isDescriptor(v) ? v : new AggregateDescriptor(v);
287 }
288
289 return o;
290 }
291 }
128 }
@@ -1,100 +1,46
1 import { ModuleResolverBase } from "./ModuleResolverBase";
2 import { Uuid } from "../Uuid";
1 import { Uuid } from "../Uuid";
3 import { argumentNotEmptyString } from "../safe";
2 import { argumentNotEmptyString } from "../safe";
4 import { TraceSource } from "../log/TraceSource";
3 import { TraceSource } from "../log/TraceSource";
5
4
6 type RequireFn = (modules: string[], cb?: (...args: any[]) => any) => void;
5 export interface RequireFn {
6 (module: string): any;
7 (modules: string[], cb?: (...args: any[]) => any): void;
8 }
7
9
8 declare const require: RequireFn;
10 declare const require: RequireFn;
9
11
12 export const rjs = require;
13
10 declare function define(name: string, modules: string[], cb?: (...args: any[]) => any, eb?: (e) => any): void;
14 declare function define(name: string, modules: string[], cb?: (...args: any[]) => any, eb?: (e) => any): void;
11 declare function define(modules: string[], cb?: (...args: any[]) => any, eb?: (e) => any): void;
15 declare function define(modules: string[], cb?: (...args: any[]) => any, eb?: (e) => any): void;
12
16
13 interface RequireJsResolverParams {
17 interface RequireJsResolverParams {
14 contextRequire: (modules: string[], cb?: (...args: any[]) => any) => void;
18 contextRequire: RequireFn;
15
16 base: string;
17 }
19 }
18
20
19 const trace = TraceSource.get("@implab/core/di/RequireJsResolver");
21 const trace = TraceSource.get("@implab/core/di/RequireJsHelper");
20
21 export class RequireJsResolver extends ModuleResolverBase {
22 _contextRequire = require;
23
24 _base: string;
25
26 constructor(opts?: RequireJsResolverParams) {
27 super();
28
29 if (opts) {
30
31 if (opts.contextRequire)
32 this._contextRequire = opts.contextRequire;
33
22
34 if (opts.base) {
23 export async function createContextRequire(moduleName: string): Promise<RequireFn> {
35 if (opts.base.indexOf("./") === 0)
24 argumentNotEmptyString(moduleName, "moduleName");
36 throw new Error(`A module id should be an absolute: '${opts.base}'`);
37 this._base = opts.base;
38 }
39 }
40
41 }
42
25
43 async createResolver(moduleName: string): Promise<ModuleResolverBase> {
26 const parts = moduleName.split("/");
44 argumentNotEmptyString(moduleName, "moduleName");
27 if (parts[0] === ".")
45
28 throw new Error("An absolute module path is required");
46 trace.log("createResolver({0})", moduleName);
47
48 const parts = moduleName.split("/");
49 if (parts[0] === ".") {
50 if (this._base)
51 parts[0] = this._base;
52 else
53 throw new Error(`Can't resolve a relative module '${moduleName}'`);
54 }
55
29
56 if (parts.length > 1)
30 if (parts.length > 1)
57 parts.splice(-1, 1, Uuid());
31 parts.splice(-1, 1, Uuid());
58 else
32 else
59 parts.push(Uuid());
33 parts.push(Uuid());
60
34
61 const shim = parts.join("/");
35 const shim = parts.join("/");
62
36
63 trace.debug(`define shim ${shim}`);
37 trace.debug(`define shim ${shim}`);
64
38
65 try {
39 return new Promise<RequireFn>(fulfill => {
66 const contextRequire = await new Promise<RequireFn>(
40 define(shim, ["require"], r => {
67 (resolve, reject) => {
41 trace.debug("shim resolved");
68 try {
42 return r;
69 define(shim, ["require"], r => {
43 });
70 trace.debug("shim resolved");
44 require([shim], fulfill);
71 resolve(r);
45 });
72 }, reject);
73 require([shim]);
74 } catch (e) {
75 reject(e);
76 }
77 }
78 );
79
80 trace.debug("creating new moduleResolver");
81
82 return new RequireJsResolver({
83 base: parts.slice(0, -1).join("/"),
84 contextRequire
85 });
86 } catch (e) {
87 trace.error(e);
88 throw e;
89 }
90
91 }
92
93 async loadModule(moduleName: string): Promise<object> {
94 trace.log(`loadModule(${moduleName})`);
95 return new Promise<object>(
96 resolve => this._contextRequire.call(null, [moduleName], resolve)
97 );
98 }
99
100 }
46 }
@@ -1,288 +1,235
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2 import { Descriptor, ActivationType, ServiceMap, isDescriptor } from "./interfaces";
2 import { Descriptor, ActivationType, ServiceMap, isDescriptor } from "./interfaces";
3 import { Container } from "./Container";
3 import { Container } from "./Container";
4 import { argumentNotNull, isPrimitive, oid, isPromise } from "../safe";
4 import { argumentNotNull, isPrimitive } from "../safe";
5 import { Constructor, Factory } from "../interfaces";
6 import { TraceSource } from "../log/TraceSource";
5 import { TraceSource } from "../log/TraceSource";
7
6
8 let cacheId = 0;
7 let cacheId = 0;
9
8
10 const trace = TraceSource.get("@implab/core/di/ActivationContext");
9 const trace = TraceSource.get("@implab/core/di/ActivationContext");
11
10
12 function injectMethod(target, method, context, args) {
11 function injectMethod(target, method, context, args) {
13 const m = target[method];
12 const m = target[method];
14 if (!m)
13 if (!m)
15 throw new Error("Method '" + method + "' not found");
14 throw new Error("Method '" + method + "' not found");
16
15
17 if (args instanceof Array)
16 if (args instanceof Array)
18 return m.apply(target, context.parse(args, "." + method));
17 return m.apply(target, context.parse(args, "." + method));
19 else
18 else
20 return m.call(target, context.parse(args, "." + method));
19 return m.call(target, context.parse(args, "." + method));
21 }
20 }
22
21
23 function makeClenupCallback(target, method: ((instance) => void) | string) {
22 function makeClenupCallback(target, method: ((instance) => void) | string) {
24 if (typeof (method) === "string") {
23 if (typeof (method) === "string") {
25 return () => {
24 return () => {
26 target[method]();
25 target[method]();
27 };
26 };
28 } else {
27 } else {
29 return () => {
28 return () => {
30 method(target);
29 method(target);
31 };
30 };
32 }
31 }
33 }
32 }
34
33
35 // TODO: make async
34 // TODO: make async
36 function _parse(value, context: ActivationContext, path: string) {
35 function _parse(value, context: ActivationContext, path: string) {
37 if (isPrimitive(value))
36 if (isPrimitive(value))
38 return value;
37 return value;
39
38
40 trace.debug("parse {0}", path);
39 trace.debug("parse {0}", path);
41
40
42 if (isDescriptor(value))
41 if (isDescriptor(value))
43 return context.activate(value, path);
42 return context.activate(value, path);
44
43
45 if (value instanceof Array)
44 if (value instanceof Array)
46 return value.map((x, i) => _parse(x, context, `${path}[${i}]`));
45 return value.map((x, i) => _parse(x, context, `${path}[${i}]`));
47
46
48 const t = {};
47 const t = {};
49 for (const p of Object.keys(value))
48 for (const p of Object.keys(value))
50 t[p] = _parse(value[p], context, `${path}.${p}`);
49 t[p] = _parse(value[p], context, `${path}.${p}`);
51
50
52 return t;
51 return t;
53 }
52 }
54
53
55 export interface ServiceDescriptorParams {
54 export interface ServiceDescriptorParams {
56 activation?: ActivationType;
55 activation?: ActivationType;
57
56
58 owner: Container;
57 owner: Container;
59
58
60 type?: Constructor;
61
62 factory?: Factory;
63
64 params?;
59 params?;
65
60
66 inject?: object[];
61 inject?: object[];
67
62
68 services?: ServiceMap;
63 services?: ServiceMap;
69
64
70 cleanup?: ((x) => void) | string;
65 cleanup?: ((x) => void) | string;
71 }
66 }
72
67
73 export class ServiceDescriptor implements Descriptor {
68 export class ServiceDescriptor implements Descriptor {
74 _instance;
69 _instance;
75
70
76 _hasInstance = false;
71 _hasInstance = false;
77
72
78 _activationType = ActivationType.Call;
73 _activationType = ActivationType.Call;
79
74
80 _services: ServiceMap;
75 _services: ServiceMap;
81
76
82 _type: Constructor = null;
83
84 _factory: Factory = null;
85
86 _params;
77 _params;
87
78
88 _inject: object[];
79 _inject: object[];
89
80
90 _cleanup: ((x) => void) | string;
81 _cleanup: ((x) => void) | string;
91
82
92 _cacheId: any;
83 _cacheId: any;
93
84
94 _owner: Container;
85 _owner: Container;
95
86
96 constructor(opts: ServiceDescriptorParams) {
87 constructor(opts: ServiceDescriptorParams) {
97 argumentNotNull(opts, "opts");
88 argumentNotNull(opts, "opts");
98 argumentNotNull(opts.owner, "owner");
89 argumentNotNull(opts.owner, "owner");
99
90
100 this._owner = opts.owner;
91 this._owner = opts.owner;
101
92
102 if (!(opts.type || opts.factory))
103 throw new Error(
104 "Either a type or a factory must be specified");
105
106 if (opts.activation)
93 if (opts.activation)
107 this._activationType = opts.activation;
94 this._activationType = opts.activation;
108
95
109 if (opts.type)
110 this._type = opts.type;
111
112 if (opts.params)
96 if (opts.params)
113 this._params = opts.params;
97 this._params = opts.params;
114
98
115 if (opts.inject)
99 if (opts.inject)
116 this._inject = opts.inject;
100 this._inject = opts.inject;
117
101
118 if (opts.services)
102 if (opts.services)
119 this._services = opts.services;
103 this._services = opts.services;
120
104
121 if (opts.factory)
122 this._factory = opts.factory;
123
124 if (opts.cleanup) {
105 if (opts.cleanup) {
125 if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function))
106 if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function))
126 throw new Error(
107 throw new Error(
127 "The cleanup parameter must be either a function or a function name");
108 "The cleanup parameter must be either a function or a function name");
128
109
129 this._cleanup = opts.cleanup;
110 this._cleanup = opts.cleanup;
130 }
111 }
131
132 if (this._activationType === ActivationType.Singleton) {
133 const tof = this._type || this._factory;
134
135 // create the persistent cache identifier for the type
136 if (isPrimitive(tof))
137 this._cacheId = tof;
138 else
139 this._cacheId = oid(tof);
140 } else {
141 this._cacheId = ++cacheId;
142 }
143 }
112 }
144
113
145 activate(context: ActivationContext) {
114 activate(context: ActivationContext) {
146 // if we have a local service records, register them first
115 // if we have a local service records, register them first
147 let instance;
116 let instance;
148
117
118 // ensure we have a cache id
119 if (!this._cacheId)
120 this._cacheId = ++cacheId;
121
149 switch (this._activationType) {
122 switch (this._activationType) {
150 case ActivationType.Singleton: // SINGLETON
123 case ActivationType.Singleton: // SINGLETON
151 // if the value is cached return it
124 // if the value is cached return it
152 if (this._hasInstance)
125 if (this._hasInstance)
153 return this._instance;
126 return this._instance;
154
127
155 // singletons are bound to the root container
128 // singletons are bound to the root container
156 const container = context.container.getRootContainer();
129 const container = context.container.getRootContainer();
157
130
158 if (container.has(this._cacheId)) {
131 if (container.has(this._cacheId)) {
159 instance = container.get(this._cacheId);
132 instance = container.get(this._cacheId);
160 } else {
133 } else {
161 instance = this._create(context);
134 instance = this._create(context);
162 container.store(this._cacheId, instance);
135 container.store(this._cacheId, instance);
163 if (this._cleanup)
136 if (this._cleanup)
164 container.onDispose(
137 container.onDispose(
165 makeClenupCallback(instance, this._cleanup));
138 makeClenupCallback(instance, this._cleanup));
166 }
139 }
167
140
168 this._hasInstance = true;
141 this._hasInstance = true;
169 return (this._instance = instance);
142 return (this._instance = instance);
170
143
171 case ActivationType.Container: // CONTAINER
144 case ActivationType.Container: // CONTAINER
172 // return a cached value
145 // return a cached value
173
146
174 if (this._hasInstance)
147 if (this._hasInstance)
175 return this._instance;
148 return this._instance;
176
149
177 // create an instance
150 // create an instance
178 instance = this._create(context);
151 instance = this._create(context);
179
152
180 // the instance is bound to the container
153 // the instance is bound to the container
181 if (this._cleanup)
154 if (this._cleanup)
182 this._owner.onDispose(
155 this._owner.onDispose(
183 makeClenupCallback(instance, this._cleanup));
156 makeClenupCallback(instance, this._cleanup));
184
157
185 // cache and return the instance
158 // cache and return the instance
186 this._hasInstance = true;
159 this._hasInstance = true;
187 return (this._instance = instance);
160 return (this._instance = instance);
188 case ActivationType.Context: // CONTEXT
161 case ActivationType.Context: // CONTEXT
189 // return a cached value if one exists
162 // return a cached value if one exists
190
163
191 if (context.has(this._cacheId))
164 if (context.has(this._cacheId))
192 return context.get(this._cacheId);
165 return context.get(this._cacheId);
193 // context context activated instances are controlled by callers
166 // context context activated instances are controlled by callers
194 return context.store(this._cacheId, this._create(context));
167 return context.store(this._cacheId, this._create(context));
195 case ActivationType.Call: // CALL
168 case ActivationType.Call: // CALL
196 // per-call created instances are controlled by callers
169 // per-call created instances are controlled by callers
197 return this._create(context);
170 return this._create(context);
198 case ActivationType.Hierarchy: // HIERARCHY
171 case ActivationType.Hierarchy: // HIERARCHY
199 // hierarchy activated instances are behave much like container activated
172 // hierarchy activated instances are behave much like container activated
200 // except they are created and bound to the child container
173 // except they are created and bound to the child container
201
174
202 // return a cached value
175 // return a cached value
203 if (context.container.has(this._cacheId))
176 if (context.container.has(this._cacheId))
204 return context.container.get(this._cacheId);
177 return context.container.get(this._cacheId);
205
178
206 instance = this._create(context);
179 instance = this._create(context);
207
180
208 if (this._cleanup)
181 if (this._cleanup)
209 context.container.onDispose(makeClenupCallback(
182 context.container.onDispose(makeClenupCallback(
210 instance,
183 instance,
211 this._cleanup));
184 this._cleanup));
212
185
213 return context.container.store(this._cacheId, instance);
186 return context.container.store(this._cacheId, instance);
214 default:
187 default:
215 throw new Error("Invalid activation type: " + this._activationType);
188 throw new Error("Invalid activation type: " + this._activationType);
216 }
189 }
217 }
190 }
218
191
219 isInstanceCreated() {
192 isInstanceCreated() {
220 return this._hasInstance;
193 return this._hasInstance;
221 }
194 }
222
195
223 getInstance() {
196 getInstance() {
224 return this._instance;
197 return this._instance;
225 }
198 }
226
199
200 _factory(...params: any[]): any {
201 throw Error("Not implemented");
202 }
203
227 _create(context: ActivationContext) {
204 _create(context: ActivationContext) {
228 trace.debug(`constructing ${context._name}`);
205 trace.debug(`constructing ${context._name}`);
229
206
230 if (this._activationType !== ActivationType.Call &&
207 if (this._activationType !== ActivationType.Call &&
231 context.visit(this._cacheId) > 0)
208 context.visit(this._cacheId) > 0)
232 throw new Error("Recursion detected");
209 throw new Error("Recursion detected");
233
210
234 if (this._services) {
211 if (this._services) {
235 for (const p in this._services)
212 for (const p in this._services)
236 context.register(p, this._services[p]);
213 context.register(p, this._services[p]);
237 }
214 }
238
215
239 let instance;
216 let instance;
240
217
241 if (!this._factory) {
242 const ctor = this._type;
243 if (this._params && this._params.length) {
244 this._factory = (...args) => {
245 const t = Object.create(ctor.prototype);
246 const inst = ctor.apply(t, args);
247 return isPrimitive(inst) ? t : inst;
248 };
249 } else {
250 this._factory = () => {
251 return new ctor();
252 };
253 }
254 }
255
256 if (this._params === undefined) {
218 if (this._params === undefined) {
257 instance = this._factory();
219 instance = this._factory();
258 } else if (this._params instanceof Array) {
220 } else if (this._params instanceof Array) {
259 instance = this._factory.apply(this, _parse(this._params, context, "args"));
221 instance = this._factory.apply(this, _parse(this._params, context, "args"));
260 } else {
222 } else {
261 instance = this._factory(_parse(this._params, context, "args"));
223 instance = this._factory(_parse(this._params, context, "args"));
262 }
224 }
263
225
264 if (this._inject) {
226 if (this._inject) {
265 this._inject.forEach(spec => {
227 this._inject.forEach(spec => {
266 for (const m in spec)
228 for (const m in spec)
267 injectMethod(instance, m, context, spec[m]);
229 injectMethod(instance, m, context, spec[m]);
268 });
230 });
269 }
231 }
270
232
271 return instance;
233 return instance;
272 }
234 }
273
274 // @constructor {singleton} foo/bar/Baz
275 // @factory {singleton}
276 toString() {
277 const parts = [];
278
279 parts.push(this._type ? "@constructor" : "@factory");
280
281 parts.push(ActivationType[this._activationType]);
282
283 if (typeof (this._type) === "string")
284 parts.push(this._type);
285
286 return parts.join(" ");
287 }
288 }
235 }
@@ -1,306 +1,308
1 let _nextOid = 0;
1 let _nextOid = 0;
2 const _oid = typeof Symbol === "function" ? Symbol("__oid") : "__oid";
2 const _oid = typeof Symbol === "function" ?
3 Symbol("__implab__oid__") :
4 "__implab__oid__";
3
5
4 export function oid(instance: object): string {
6 export function oid(instance: object): string {
5 if (isNull(instance))
7 if (isNull(instance))
6 return null;
8 return null;
7
9
8 if (_oid in instance)
10 if (_oid in instance)
9 return instance[_oid];
11 return instance[_oid];
10 else
12 else
11 return (instance[_oid] = "oid_" + (++_nextOid));
13 return (instance[_oid] = "oid_" + (++_nextOid));
12 }
14 }
13
15
14 export function argumentNotNull(arg, name) {
16 export function argumentNotNull(arg, name) {
15 if (arg === null || arg === undefined)
17 if (arg === null || arg === undefined)
16 throw new Error("The argument " + name + " can't be null or undefined");
18 throw new Error("The argument " + name + " can't be null or undefined");
17 }
19 }
18
20
19 export function argumentNotEmptyString(arg, name) {
21 export function argumentNotEmptyString(arg, name) {
20 if (typeof (arg) !== "string" || !arg.length)
22 if (typeof (arg) !== "string" || !arg.length)
21 throw new Error("The argument '" + name + "' must be a not empty string");
23 throw new Error("The argument '" + name + "' must be a not empty string");
22 }
24 }
23
25
24 export function argumentNotEmptyArray(arg, name) {
26 export function argumentNotEmptyArray(arg, name) {
25 if (!(arg instanceof Array) || !arg.length)
27 if (!(arg instanceof Array) || !arg.length)
26 throw new Error("The argument '" + name + "' must be a not empty array");
28 throw new Error("The argument '" + name + "' must be a not empty array");
27 }
29 }
28
30
29 export function argumentOfType(arg, type, name) {
31 export function argumentOfType(arg, type, name) {
30 if (!(arg instanceof type))
32 if (!(arg instanceof type))
31 throw new Error("The argument '" + name + "' type doesn't match");
33 throw new Error("The argument '" + name + "' type doesn't match");
32 }
34 }
33
35
34 export function isNull(arg) {
36 export function isNull(arg) {
35 return (arg === null || arg === undefined);
37 return (arg === null || arg === undefined);
36 }
38 }
37
39
38 export function isPrimitive(arg) {
40 export function isPrimitive(arg) {
39 return (arg === null || arg === undefined || typeof (arg) === "string" ||
41 return (arg === null || arg === undefined || typeof (arg) === "string" ||
40 typeof (arg) === "number" || typeof (arg) === "boolean");
42 typeof (arg) === "number" || typeof (arg) === "boolean");
41 }
43 }
42
44
43 export function isInteger(arg) {
45 export function isInteger(arg) {
44 return parseInt(arg, 10) === arg;
46 return parseInt(arg, 10) === arg;
45 }
47 }
46
48
47 export function isNumber(arg) {
49 export function isNumber(arg) {
48 return parseFloat(arg) === arg;
50 return parseFloat(arg) === arg;
49 }
51 }
50
52
51 export function isString(val) {
53 export function isString(val) {
52 return typeof (val) === "string" || val instanceof String;
54 return typeof (val) === "string" || val instanceof String;
53 }
55 }
54
56
55 export function isPromise(val): val is PromiseLike<any> {
57 export function isPromise(val): val is PromiseLike<any> {
56 return "then" in val && val.then instanceof Function;
58 return "then" in val && val.then instanceof Function;
57 }
59 }
58
60
59 export function isNullOrEmptyString(str) {
61 export function isNullOrEmptyString(str) {
60 if (str === null || str === undefined ||
62 if (str === null || str === undefined ||
61 ((typeof (str) === "string" || str instanceof String) && str.length === 0))
63 ((typeof (str) === "string" || str instanceof String) && str.length === 0))
62 return true;
64 return true;
63 }
65 }
64
66
65 export function isNotEmptyArray(arg): arg is Array<any> {
67 export function isNotEmptyArray(arg): arg is Array<any> {
66 return (arg instanceof Array && arg.length > 0);
68 return (arg instanceof Array && arg.length > 0);
67 }
69 }
68
70
69 export function getGlobal() {
71 export function getGlobal() {
70 return this;
72 return this;
71 }
73 }
72
74
73 export function get(member: string, context?: object) {
75 export function get(member: string, context?: object) {
74 argumentNotEmptyString(member, "member");
76 argumentNotEmptyString(member, "member");
75 let that = context || getGlobal();
77 let that = context || getGlobal();
76 const parts = member.split(".");
78 const parts = member.split(".");
77 for (const m of parts) {
79 for (const m of parts) {
78 if (!m)
80 if (!m)
79 continue;
81 continue;
80 if (isNull(that = that[m]))
82 if (isNull(that = that[m]))
81 break;
83 break;
82 }
84 }
83 return that;
85 return that;
84 }
86 }
85
87
86 /**
88 /**
87 * Выполняет метод для каждого элемента массива, останавливается, когда
89 * Выполняет метод для каждого элемента массива, останавливается, когда
88 * либо достигнут конец массива, либо функция <c>cb</c> вернула
90 * либо достигнут конец массива, либо функция <c>cb</c> вернула
89 * значение.
91 * значение.
90 *
92 *
91 * @param {Array | Object} obj массив элементов для просмотра
93 * @param {Array | Object} obj массив элементов для просмотра
92 * @param {Function} cb функция, вызываемая для каждого элемента
94 * @param {Function} cb функция, вызываемая для каждого элемента
93 * @param {Object} thisArg значение, которое будет передано в качестве
95 * @param {Object} thisArg значение, которое будет передано в качестве
94 * <c>this</c> в <c>cb</c>.
96 * <c>this</c> в <c>cb</c>.
95 * @returns Результат вызова функции <c>cb</c>, либо <c>undefined</c>
97 * @returns Результат вызова функции <c>cb</c>, либо <c>undefined</c>
96 * если достигнут конец массива.
98 * если достигнут конец массива.
97 */
99 */
98 export function each(obj, cb, thisArg?) {
100 export function each(obj, cb, thisArg?) {
99 argumentNotNull(cb, "cb");
101 argumentNotNull(cb, "cb");
100 if (obj instanceof Array) {
102 if (obj instanceof Array) {
101 for (let i = 0; i < obj.length; i++) {
103 for (let i = 0; i < obj.length; i++) {
102 const x = cb.call(thisArg, obj[i], i);
104 const x = cb.call(thisArg, obj[i], i);
103 if (x !== undefined)
105 if (x !== undefined)
104 return x;
106 return x;
105 }
107 }
106 } else {
108 } else {
107 const keys = Object.keys(obj);
109 const keys = Object.keys(obj);
108 for (const k of keys) {
110 for (const k of keys) {
109 const x = cb.call(thisArg, obj[k], k);
111 const x = cb.call(thisArg, obj[k], k);
110 if (x !== undefined)
112 if (x !== undefined)
111 return x;
113 return x;
112 }
114 }
113 }
115 }
114 }
116 }
115
117
116 /** Copies property values from a source object to the destination and returns
118 /** Copies property values from a source object to the destination and returns
117 * the destination onject.
119 * the destination onject.
118 *
120 *
119 * @param dest The destination object into which properties from the source
121 * @param dest The destination object into which properties from the source
120 * object will be copied.
122 * object will be copied.
121 * @param source The source of values which will be copied to the destination
123 * @param source The source of values which will be copied to the destination
122 * object.
124 * object.
123 * @param template An optional parameter specifies which properties should be
125 * @param template An optional parameter specifies which properties should be
124 * copied from the source and how to map them to the destination. If the
126 * copied from the source and how to map them to the destination. If the
125 * template is an array it contains the list of property names to copy from the
127 * template is an array it contains the list of property names to copy from the
126 * source to the destination. In case of object the templates contains the map
128 * source to the destination. In case of object the templates contains the map
127 * where keys are property names in the source and the values are property
129 * where keys are property names in the source and the values are property
128 * names in the destination object. If the template isn't specified then the
130 * names in the destination object. If the template isn't specified then the
129 * own properties of the source are entirely copied to the destination.
131 * own properties of the source are entirely copied to the destination.
130 *
132 *
131 */
133 */
132 export function mixin<T, S>(dest: T, source: S, template?: string[] | object): T & S {
134 export function mixin<T, S>(dest: T, source: S, template?: string[] | object): T & S {
133 argumentNotNull(dest, "to");
135 argumentNotNull(dest, "to");
134 const _res = dest as T & S;
136 const _res = dest as T & S;
135
137
136 if (template instanceof Array) {
138 if (template instanceof Array) {
137 for (const p of template) {
139 for (const p of template) {
138 if (p in source)
140 if (p in source)
139 _res[p] = source[p];
141 _res[p] = source[p];
140 }
142 }
141 } else if (template) {
143 } else if (template) {
142 const keys = Object.keys(source);
144 const keys = Object.keys(source);
143 for (const p of keys) {
145 for (const p of keys) {
144 if (p in template)
146 if (p in template)
145 _res[template[p]] = source[p];
147 _res[template[p]] = source[p];
146 }
148 }
147 } else {
149 } else {
148 const keys = Object.keys(source);
150 const keys = Object.keys(source);
149 for (const p of keys)
151 for (const p of keys)
150 _res[p] = source[p];
152 _res[p] = source[p];
151 }
153 }
152
154
153 return _res;
155 return _res;
154 }
156 }
155
157
156 /** Wraps the specified function to emulate an asynchronous execution.
158 /** Wraps the specified function to emulate an asynchronous execution.
157 * @param{Object} thisArg [Optional] Object which will be passed as 'this' to the function.
159 * @param{Object} thisArg [Optional] Object which will be passed as 'this' to the function.
158 * @param{Function|String} fn [Required] Function wich will be wrapped.
160 * @param{Function|String} fn [Required] Function wich will be wrapped.
159 */
161 */
160 export function async(_fn: (...args: any[]) => any, thisArg): (...args: any[]) => PromiseLike<any> {
162 export function async(_fn: (...args: any[]) => any, thisArg): (...args: any[]) => PromiseLike<any> {
161 let fn = _fn;
163 let fn = _fn;
162
164
163 if (arguments.length === 2 && !(fn instanceof Function))
165 if (arguments.length === 2 && !(fn instanceof Function))
164 fn = thisArg[fn];
166 fn = thisArg[fn];
165
167
166 if (fn == null)
168 if (fn == null)
167 throw new Error("The function must be specified");
169 throw new Error("The function must be specified");
168
170
169 function wrapresult(x, e?): PromiseLike<any> {
171 function wrapresult(x, e?): PromiseLike<any> {
170 if (e) {
172 if (e) {
171 return {
173 return {
172 then(cb, eb) {
174 then(cb, eb) {
173 try {
175 try {
174 return eb ? wrapresult(eb(e)) : this;
176 return eb ? wrapresult(eb(e)) : this;
175 } catch (e2) {
177 } catch (e2) {
176 return wrapresult(null, e2);
178 return wrapresult(null, e2);
177 }
179 }
178 }
180 }
179 };
181 };
180 } else {
182 } else {
181 if (x && x.then)
183 if (x && x.then)
182 return x;
184 return x;
183 return {
185 return {
184 then(cb) {
186 then(cb) {
185 try {
187 try {
186 return cb ? wrapresult(cb(x)) : this;
188 return cb ? wrapresult(cb(x)) : this;
187 } catch (e2) {
189 } catch (e2) {
188 return wrapresult(e2);
190 return wrapresult(e2);
189 }
191 }
190 }
192 }
191 };
193 };
192 }
194 }
193 }
195 }
194
196
195 return (...args) => {
197 return (...args) => {
196 try {
198 try {
197 return wrapresult(fn.apply(thisArg, args));
199 return wrapresult(fn.apply(thisArg, args));
198 } catch (e) {
200 } catch (e) {
199 return wrapresult(null, e);
201 return wrapresult(null, e);
200 }
202 }
201 };
203 };
202 }
204 }
203
205
204 type _AnyFn = (...args) => any;
206 type _AnyFn = (...args) => any;
205
207
206 export function delegate<T, K extends keyof T>(target: T, _method: (K | _AnyFn)) {
208 export function delegate<T, K extends keyof T>(target: T, _method: (K | _AnyFn)) {
207 let method;
209 let method;
208
210
209 if (!(_method instanceof Function)) {
211 if (!(_method instanceof Function)) {
210 argumentNotNull(target, "target");
212 argumentNotNull(target, "target");
211 method = target[_method];
213 method = target[_method];
212 if (!(method instanceof Function))
214 if (!(method instanceof Function))
213 throw new Error("'method' argument must be a Function or a method name");
215 throw new Error("'method' argument must be a Function or a method name");
214 } else {
216 } else {
215 method = _method;
217 method = _method;
216 }
218 }
217
219
218 return (...args) => {
220 return (...args) => {
219 return method.apply(target, args);
221 return method.apply(target, args);
220 };
222 };
221 }
223 }
222
224
223 /**
225 /**
224 * Для каждого элемента массива вызывает указанную функцию и сохраняет
226 * Для каждого элемента массива вызывает указанную функцию и сохраняет
225 * возвращенное значение в массиве результатов.
227 * возвращенное значение в массиве результатов.
226 *
228 *
227 * @remarks cb может выполняться асинхронно, при этом одновременно будет
229 * @remarks cb может выполняться асинхронно, при этом одновременно будет
228 * только одна операция.
230 * только одна операция.
229 *
231 *
230 * @async
232 * @async
231 */
233 */
232 export function pmap(items, cb) {
234 export function pmap(items, cb) {
233 argumentNotNull(cb, "cb");
235 argumentNotNull(cb, "cb");
234
236
235 if (isPromise(items))
237 if (isPromise(items))
236 return items.then(data => pmap(data, cb));
238 return items.then(data => pmap(data, cb));
237
239
238 if (isNull(items) || !items.length)
240 if (isNull(items) || !items.length)
239 return items;
241 return items;
240
242
241 let i = 0;
243 let i = 0;
242 const result = [];
244 const result = [];
243
245
244 function next() {
246 function next() {
245 let r;
247 let r;
246 let ri;
248 let ri;
247
249
248 function chain(x) {
250 function chain(x) {
249 result[ri] = x;
251 result[ri] = x;
250 return next();
252 return next();
251 }
253 }
252
254
253 while (i < items.length) {
255 while (i < items.length) {
254 r = cb(items[i], i);
256 r = cb(items[i], i);
255 ri = i;
257 ri = i;
256 i++;
258 i++;
257 if (isPromise(r)) {
259 if (isPromise(r)) {
258 return r.then(chain);
260 return r.then(chain);
259 } else {
261 } else {
260 result[ri] = r;
262 result[ri] = r;
261 }
263 }
262 }
264 }
263 return result;
265 return result;
264 }
266 }
265
267
266 return next();
268 return next();
267 }
269 }
268
270
269 /**
271 /**
270 * Выбирает первый элемент из последовательности, или обещания, если в
272 * Выбирает первый элемент из последовательности, или обещания, если в
271 * качестве параметра используется обещание, оно должно вернуть массив.
273 * качестве параметра используется обещание, оно должно вернуть массив.
272 *
274 *
273 * @param {Function} cb обработчик результата, ему будет передан первый
275 * @param {Function} cb обработчик результата, ему будет передан первый
274 * элемент последовательности в случае успеха
276 * элемент последовательности в случае успеха
275 * @param {Function} err обработчик исключения, если массив пустой, либо
277 * @param {Function} err обработчик исключения, если массив пустой, либо
276 * не массив
278 * не массив
277 *
279 *
278 * @remarks Если не указаны ни cb ни err, тогда функция вернет либо
280 * @remarks Если не указаны ни cb ни err, тогда функция вернет либо
279 * обещание, либо первый элемент.
281 * обещание, либо первый элемент.
280 * @async
282 * @async
281 */
283 */
282 export function first(sequence, cb: (x) => any, err: (x) => any) {
284 export function first(sequence, cb: (x) => any, err: (x) => any) {
283 if (sequence) {
285 if (sequence) {
284 if (isPromise(sequence)) {
286 if (isPromise(sequence)) {
285 return sequence.then(res => first(res, cb, err));
287 return sequence.then(res => first(res, cb, err));
286 } else if (sequence && "length" in sequence) {
288 } else if (sequence && "length" in sequence) {
287 if (sequence.length === 0) {
289 if (sequence.length === 0) {
288 if (err)
290 if (err)
289 return err(new Error("The sequence is empty"));
291 return err(new Error("The sequence is empty"));
290 else
292 else
291 throw new Error("The sequence is empty");
293 throw new Error("The sequence is empty");
292 }
294 }
293 return cb ? cb(sequence[0]) : sequence[0];
295 return cb ? cb(sequence[0]) : sequence[0];
294 }
296 }
295 }
297 }
296
298
297 if (err)
299 if (err)
298 return err(new Error("The sequence is required"));
300 return err(new Error("The sequence is required"));
299 else
301 else
300 throw new Error("The sequence is required");
302 throw new Error("The sequence is required");
301 }
303 }
302
304
303 export function destroy(d) {
305 export function destroy(d) {
304 if (d && "destroy" in d)
306 if (d && "destroy" in d)
305 d.destroy();
307 d.destroy();
306 }
308 }
@@ -1,17 +1,17
1 define({
1 define({
2 foo: {
2 foo: {
3 $type: "./Foo#Foo"
3 $type: "./Foo:Foo"
4 },
4 },
5
5
6 bar: {
6 bar: {
7 $type: "./Bar#Bar",
7 $type: "./Bar:Bar",
8 params: {
8 params: {
9 db: {
9 db: {
10 provider: {
10 provider: {
11 $dependency: "db"
11 $dependency: "db"
12 }
12 }
13 }
13 }
14 }
14 }
15 },
15 },
16 db: "db://localhost"
16 db: "db://localhost"
17 }); No newline at end of file
17 });
@@ -1,118 +1,93
1 import { test, TapeWriter } from "./TestTraits";
1 import { test, TapeWriter } from "./TestTraits";
2 import { Container } from "@implab/core/di/Container";
2 import { Container } from "@implab/core/di/Container";
3 import { ReferenceDescriptor } from "@implab/core/di/ReferenceDescriptor";
3 import { ReferenceDescriptor } from "@implab/core/di/ReferenceDescriptor";
4 import { AggregateDescriptor } from "@implab/core/di/AggregateDescriptor";
4 import { AggregateDescriptor } from "@implab/core/di/AggregateDescriptor";
5 import { ValueDescriptor } from "@implab/core/di/ValueDescriptor";
5 import { ValueDescriptor } from "@implab/core/di/ValueDescriptor";
6 import { TraceSource, DebugLevel } from "@implab/core/log/TraceSource";
6 import { TraceSource, DebugLevel } from "@implab/core/log/TraceSource";
7 import { Foo } from "./mock/Foo";
7 import { Foo } from "./mock/Foo";
8 import { Bar } from "./mock/Bar";
8 import { Bar } from "./mock/Bar";
9 import { isNull } from "@implab/core/safe";
9 import { isNull } from "@implab/core/safe";
10
10
11 test("Container register/resolve tests", async t => {
11 test("Container register/resolve tests", async t => {
12 const writer = new TapeWriter(t);
13
14 TraceSource.on(ts => {
15 ts.level = DebugLevel;
16 writer.writeEvents(ts.events);
17 });
18
19 const container = new Container();
12 const container = new Container();
20
13
21 const connection1 = "db://localhost";
14 const connection1 = "db://localhost";
22
15
23 t.throws(
16 t.throws(
24 () => container.register("bla-bla", "bla-bla"),
17 () => container.register("bla-bla", "bla-bla"),
25 "Do not allow to register anything other than descriptors"
18 "Do not allow to register anything other than descriptors"
26 );
19 );
27
20
28 t.doesNotThrow(
21 t.doesNotThrow(
29 () => container.register("connection", new ValueDescriptor(connection1)),
22 () => container.register("connection", new ValueDescriptor(connection1)),
30 "register ValueDescriptor"
23 "register ValueDescriptor"
31 );
24 );
32
25
33 t.equals(container.getService("connection"), connection1, "resolve string value");
26 t.equals(container.resolve("connection"), connection1, "resolve string value");
34
27
35 t.doesNotThrow(
28 t.doesNotThrow(
36 () => container.register(
29 () => container.register(
37 "dbParams",
30 "dbParams",
38 new AggregateDescriptor({
31 new AggregateDescriptor({
39 timeout: 10,
32 timeout: 10,
40 connection: new ReferenceDescriptor({ name: "connection" })
33 connection: new ReferenceDescriptor({ name: "connection" })
41 })
34 })
42 ),
35 ),
43 "register AggregateDescriptor"
36 "register AggregateDescriptor"
44 );
37 );
45
38
46 const dbParams = container.getService("dbParams");
39 const dbParams = container.resolve("dbParams");
47 t.equals(dbParams.connection, connection1, "should get string value 'dbParams.connection'");
40 t.equals(dbParams.connection, connection1, "should get string value 'dbParams.connection'");
48
49 writer.destroy();
50 });
41 });
51
42
52 test("Container configure/resolve tests", async t => {
43 test("Container configure/resolve tests", async t => {
53 const writer = new TapeWriter(t);
54
55 TraceSource.on(ts => {
56 ts.level = DebugLevel;
57 writer.writeEvents(ts.events);
58 });
59
44
60 const container = new Container();
45 const container = new Container();
61
46
62 await container.configure({
47 await container.configure({
63 foo: {
48 foo: {
64 $type: Foo
49 $type: Foo
65 },
50 },
66
51
67 box: {
52 box: {
68 $type: Bar,
53 $type: Bar,
69 params: {
54 params: {
70 $dependency: "foo"
55 $dependency: "foo"
71 }
56 }
72 },
57 },
73
58
74 bar: {
59 bar: {
75 $type: Bar,
60 $type: Bar,
76 params: {
61 params: {
77 db: {
62 db: {
78 provider: {
63 provider: {
79 $dependency: "db"
64 $dependency: "db"
80 }
65 }
81 }
66 }
82 }
67 }
83 }
68 }
84 });
69 });
85 t.pass("should configure from js object");
70 t.pass("should configure from js object");
86
71
87 const f1 = container.resolve("foo");
72 const f1 = container.resolve("foo");
88
73
89 t.assert(!isNull(f1), "foo should be not null");
74 t.assert(!isNull(f1), "foo should be not null");
90
75
91 t.throws(() => container.resolve("bar"), "should not resolve dependency 'db'");
76 t.throws(() => container.resolve("bar"), "should not resolve dependency 'db'");
92
77
93 writer.destroy();
94 });
78 });
95
79
96 test("Load configuration from module", async t => {
80 test("Load configuration from module", async t => {
97 const writer = new TapeWriter(t);
98
99 TraceSource.on(ts => {
100 ts.level = DebugLevel;
101 writer.writeEvents(ts.events);
102 });
103
104 const container = new Container();
81 const container = new Container();
105
82
106 await container.configure("test/mock/config1");
83 await container.configure("test/mock/config1");
107 t.pass("The configuration should load");
84 t.pass("The configuration should load");
108
85
109 const f1 = container.resolve("foo");
86 const f1 = container.resolve("foo");
110
87
111 t.assert(!isNull(f1), "foo should be not null");
88 t.assert(!isNull(f1), "foo should be not null");
112
89
113 const b1 = container.resolve("bar");
90 const b1 = container.resolve("bar");
114
91
115 t.assert(!isNull(b1), "foo should be not null");
92 t.assert(!isNull(b1), "foo should be not null");
116
117 writer.destroy();
118 });
93 });
@@ -1,79 +1,89
1 import { IObservable, ICancellation, IDestroyable } from "@implab/core/interfaces";
1 import { IObservable, ICancellation, IDestroyable } from "@implab/core/interfaces";
2 import { Cancellation } from "@implab/core/Cancellation";
2 import { Cancellation } from "@implab/core/Cancellation";
3 import { TraceEvent, LogLevel, WarnLevel } from "@implab/core/log/TraceSource";
3 import { TraceEvent, LogLevel, WarnLevel, DebugLevel, TraceSource } from "@implab/core/log/TraceSource";
4 import * as tape from "tape";
4 import * as tape from "tape";
5 import { argumentNotNull } from "@implab/core/safe";
5 import { argumentNotNull } from "@implab/core/safe";
6
6
7 export class TapeWriter implements IDestroyable {
7 export class TapeWriter implements IDestroyable {
8 readonly _tape: tape.Test;
8 readonly _tape: tape.Test;
9
9
10 _subscriptions = new Array<IDestroyable>();
10 _subscriptions = new Array<IDestroyable>();
11
11
12 constructor(t: tape.Test) {
12 constructor(t: tape.Test) {
13 argumentNotNull(t, "tape");
13 argumentNotNull(t, "tape");
14 this._tape = t;
14 this._tape = t;
15 }
15 }
16
16
17 writeEvents(source: IObservable<TraceEvent>, ct: ICancellation = Cancellation.none) {
17 writeEvents(source: IObservable<TraceEvent>, ct: ICancellation = Cancellation.none) {
18 const subscription = source.on(this.writeEvent.bind(this));
18 const subscription = source.on(this.writeEvent.bind(this));
19 if (ct.isSupported()) {
19 if (ct.isSupported()) {
20 ct.register(subscription.destroy.bind(subscription));
20 ct.register(subscription.destroy.bind(subscription));
21 }
21 }
22 this._subscriptions.push(subscription);
22 this._subscriptions.push(subscription);
23 }
23 }
24
24
25 writeEvent(next: TraceEvent) {
25 writeEvent(next: TraceEvent) {
26 if (next.level >= LogLevel) {
26 if (next.level >= DebugLevel) {
27 this._tape.comment("LOG " + next.arg);
27 this._tape.comment(`DEBUG ${next.source.id} ${next.arg}`);
28 } else if (next.level >= LogLevel) {
29 this._tape.comment(`LOG ${next.source.id} ${next.arg}`);
28 } else if (next.level >= WarnLevel) {
30 } else if (next.level >= WarnLevel) {
29 this._tape.comment("WARN " + next.arg);
31 this._tape.comment(`WARN ${next.source.id} ${next.arg}`);
30 } else {
32 } else {
31 this._tape.comment("ERROR " + next.arg);
33 this._tape.comment(`ERROR ${next.source.id} ${next.arg}`);
32 }
34 }
33 }
35 }
34
36
35 destroy() {
37 destroy() {
36 this._subscriptions.forEach(x => x.destroy());
38 this._subscriptions.forEach(x => x.destroy());
37 }
39 }
38 }
40 }
39
41
40 export async function delay(timeout: number, ct: ICancellation = Cancellation.none) {
42 export async function delay(timeout: number, ct: ICancellation = Cancellation.none) {
41 let un: IDestroyable;
43 let un: IDestroyable;
42
44
43 try {
45 try {
44 await new Promise((resolve, reject) => {
46 await new Promise((resolve, reject) => {
45 if (ct.isRequested()) {
47 if (ct.isRequested()) {
46 un = ct.register(reject);
48 un = ct.register(reject);
47 } else {
49 } else {
48 const ht = setTimeout(() => {
50 const ht = setTimeout(() => {
49 resolve();
51 resolve();
50 }, timeout);
52 }, timeout);
51
53
52 un = ct.register(e => {
54 un = ct.register(e => {
53 clearTimeout(ht);
55 clearTimeout(ht);
54 reject(e);
56 reject(e);
55 });
57 });
56 }
58 }
57 });
59 });
58 } finally {
60 } finally {
59 if (un)
61 if (un)
60 un.destroy();
62 un.destroy();
61 }
63 }
62 }
64 }
63
65
64 export function test(name: string, cb: (t: tape.Test) => any) {
66 export function test(name: string, cb: (t: tape.Test) => any) {
65 tape(name, async t => {
67 tape(name, async t => {
68 const writer = new TapeWriter(t);
69
70 TraceSource.on(ts => {
71 ts.level = DebugLevel;
72 writer.writeEvents(ts.events);
73 });
74
66 try {
75 try {
67 await cb(t);
76 await cb(t);
68 } catch (e) {
77 } catch (e) {
69
78
70 // verbose error information
79 // verbose error information
71 // tslint:disable-next-line
80 // tslint:disable-next-line
72 console.error(e);
81 console.error(e);
73 t.fail(e);
82 t.fail(e);
74
83
75 } finally {
84 } finally {
76 t.end();
85 t.end();
86 writer.destroy();
77 }
87 }
78 });
88 });
79 }
89 }
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now