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