##// 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,6 +1,6
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;
@@ -1,10 +1,48
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;
@@ -20,8 +58,103 export class Configuration {
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)
@@ -30,14 +163,24 export class Configuration {
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> {
@@ -59,37 +202,64 export class Configuration {
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) {
83 this._path.push("inject");
249 this._path.push("inject");
84 opts.inject = Promise.all(data.inject.map((x, i) => this._visitObject(x, i)));
250 opts.inject = mapAll(
85 this._path.pop();
251 data.inject instanceof Array ?
86 } else {
252 data.inject :
87 opts.inject = [this._visitObject(data.inject, "inject")];
253 [data.inject],
88 }
254 delegate(this, "_visitObject")
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") {
@@ -120,23 +290,64 export class Configuration {
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);
133 }
328 }
134
329
135 async _visitTypeRegistration(item: TypeRegistration, name: _key) {
330 const d = new TypeServiceDescriptor(
136 argumentNotEmptyString(item.$type, "item.$type");
331 await mapAll(opts)
332 );
333
334 this._leave();
335
336 return d;
137 }
337 }
138
338
139 async _visitFactoryRegistration(item: FactoryRegistration, name: _key) {
339 async _visitFactoryRegistration(data: FactoryRegistration, name: _key) {
140 argumentNotEmptyString(item.$factory, "item.$type");
340 argumentOfType(data.$factory, Function, "data.$type");
341 this._enter(name);
342
343 const opts = this._makeServiceParams(data);
344 opts.factory = opts.$factory;
345
346 const d = new FactoryServiceDescriptor(
347 await mapAll(opts)
348 );
349
350 this._leave();
351 return d;
141 }
352 }
142 }
353 }
@@ -1,15 +1,10
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
@@ -24,8 +19,6 export class 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) : {};
@@ -33,7 +26,6 export class Container {
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() {
@@ -45,6 +37,7 export class Container {
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)
@@ -64,7 +57,7 export class Container {
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
@@ -106,15 +99,13 export class Container {
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
@@ -134,158 +125,4 export class Container {
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 }
128 }
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 }
@@ -1,57 +1,31
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
22
31 if (opts.contextRequire)
23 export async function createContextRequire(moduleName: string): Promise<RequireFn> {
32 this._contextRequire = opts.contextRequire;
33
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 }
42
43 async createResolver(moduleName: string): Promise<ModuleResolverBase> {
44 argumentNotEmptyString(moduleName, "moduleName");
24 argumentNotEmptyString(moduleName, "moduleName");
45
25
46 trace.log("createResolver({0})", moduleName);
47
48 const parts = moduleName.split("/");
26 const parts = moduleName.split("/");
49 if (parts[0] === ".") {
27 if (parts[0] === ".")
50 if (this._base)
28 throw new Error("An absolute module path is required");
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());
@@ -62,39 +36,11 export class RequireJsResolver extends M
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>(
67 (resolve, reject) => {
68 try {
69 define(shim, ["require"], r => {
40 define(shim, ["require"], r => {
70 trace.debug("shim resolved");
41 trace.debug("shim resolved");
71 resolve(r);
42 return 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 });
43 });
86 } catch (e) {
44 require([shim], fulfill);
87 trace.error(e);
45 });
88 throw e;
89 }
90
91 }
46 }
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 }
@@ -1,8 +1,7
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;
@@ -57,10 +56,6 export interface ServiceDescriptorParams
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[];
@@ -79,10 +74,6 export class ServiceDescriptor implement
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[];
@@ -99,16 +90,9 export class ServiceDescriptor implement
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
@@ -118,9 +102,6 export class ServiceDescriptor implement
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(
@@ -128,24 +109,16 export class ServiceDescriptor implement
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
@@ -224,6 +197,10 export class ServiceDescriptor implement
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
@@ -238,21 +215,6 export class ServiceDescriptor implement
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) {
@@ -270,19 +232,4 export class ServiceDescriptor implement
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 }
235 }
288 }
@@ -1,5 +1,7
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))
@@ -1,10 +1,10
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: {
@@ -9,13 +9,6 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";
@@ -30,7 +23,7 test("Container register/resolve tests",
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(
@@ -43,19 +36,11 test("Container register/resolve tests",
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
@@ -90,17 +75,9 test("Container configure/resolve tests"
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");
@@ -113,6 +90,4 test("Load configuration from module", a
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,6 +1,6
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
@@ -23,12 +23,14 export class TapeWriter implements IDest
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
@@ -63,6 +65,13 export async function delay(timeout: num
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) {
@@ -74,6 +83,7 export function test(name: string, cb: (
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