##// END OF EJS Templates
Container.configure sync/async tests
cin -
r42:3a5e68edd843 di-typescript
parent child
Show More
@@ -0,0 +1,17
1 define({
2 foo: {
3 $type: "./Foo#Foo"
4 },
5
6 bar: {
7 $type: "./Bar#Bar",
8 params: {
9 db: {
10 provider: {
11 $dependency: "db"
12 }
13 }
14 }
15 },
16 db: "db://localhost"
17 }); No newline at end of file
@@ -1,132 +1,132
1 1 import { TraceSource } from "../log/TraceSource";
2 2 import { argumentNotNull, argumentNotEmptyString, isPrimitive, each, isNull } from "../safe";
3 import { Descriptor, ServiceMap, isDescriptor } from "./interfaces";
3 import { Descriptor, ServiceMap } from "./interfaces";
4 4 import { Container } from "./Container";
5 5
6 6 const trace = TraceSource.get("@implab/core/di/ActivationContext");
7 7
8 8 export interface ActivationContextInfo {
9 9 name: string;
10 10
11 11 service: string;
12 12
13 13 scope: ServiceMap;
14 14 }
15 15
16 16 export class ActivationContext {
17 17 _cache: object;
18 18
19 19 _services: ServiceMap;
20 20
21 21 _stack: ActivationContextInfo[];
22 22
23 23 _visited: object;
24 24
25 25 _name: string;
26 26
27 27 _localized: boolean;
28 28
29 29 container: Container;
30 30
31 31 constructor(container: Container, services: ServiceMap, name?: string, cache?: object, visited?) {
32 32 argumentNotNull(container, "container");
33 33 argumentNotNull(services, "services");
34 34
35 35 this._name = name;
36 36 this._visited = visited || {};
37 37 this._stack = [];
38 38 this._cache = cache || {};
39 39 this._services = services;
40 40 this.container = container;
41 41 }
42 42
43 43 getName() {
44 44 return this._name;
45 45 }
46 46
47 47 resolve(name, def?): any {
48 48 const d = this._services[name];
49 49
50 50 if (!d)
51 51 if (arguments.length > 1)
52 52 return def;
53 53 else
54 54 throw new Error(`Service ${name} not found`);
55 55
56 56 return this.activate(d, name);
57 57 }
58 58
59 59 /**
60 60 * registers services local to the the activation context
61 61 *
62 62 * @name{string} the name of the service
63 63 * @service{string} the service descriptor to register
64 64 */
65 65 register(name: string, service: Descriptor) {
66 66 argumentNotEmptyString(name, "name");
67 67
68 68 this._services[name] = service;
69 69 }
70 70
71 71 clone() {
72 72 return new ActivationContext(
73 73 this.container,
74 74 this._services,
75 75 this._name,
76 76 this._cache,
77 77 this._visited
78 78 );
79 79 }
80 80
81 81 has(id: string) {
82 82 return id in this._cache;
83 83 }
84 84
85 85 get(id: string) {
86 86 return this._cache[id];
87 87 }
88 88
89 89 store(id: string, value) {
90 90 return (this._cache[id] = value);
91 91 }
92 92
93 93 activate(d: Descriptor, name: string) {
94 94 if (trace.isLogEnabled())
95 95 trace.log(`enter ${name} ${d}`);
96 96
97 97 this.enter(name, d.toString());
98 98 const v = d.activate(this);
99 99 this.leave();
100 100
101 101 if (trace.isLogEnabled())
102 102 trace.log(`leave ${name}`);
103 103
104 104 return v;
105 105 }
106 106
107 107 visit(id: string) {
108 108 const count = this._visited[id] || 0;
109 109 this._visited[id] = count + 1;
110 110 return count;
111 111 }
112 112
113 113 getStack() {
114 114 return this._stack.slice().reverse();
115 115 }
116 116
117 117 private enter(name: string, service: string) {
118 118 this._stack.push({
119 119 name,
120 120 service,
121 121 scope: this._services
122 122 });
123 123 this._name = name;
124 124 this._services = Object.create(this._services);
125 125 }
126 126
127 127 private leave() {
128 128 const ctx = this._stack.pop();
129 129 this._services = ctx.scope;
130 130 this._name = ctx.name;
131 131 }
132 132 }
@@ -1,279 +1,291
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 } from "./interfaces";
4 import { isDescriptor, ActivationType, ServiceMap, isDependencyRegistration, isValueRegistration, ServiceRegistration, DependencyRegistration, ValueRegistration } from "./interfaces";
5 5 import { AggregateDescriptor } from "./AggregateDescriptor";
6 6 import { isPrimitive, pmap } from "../safe";
7 7 import { ReferenceDescriptor } from "./ReferenceDescriptor";
8 8 import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor";
9 9 import { ModuleResolverBase } from "./ModuleResolverBase";
10 10 import format = require("../text/format");
11 import { throws } from "assert";
11 import { TraceSource } from "../log/TraceSource";
12 import { RequireJsResolver } from "./RequireJsResolver";
13
14 const trace = TraceSource.get("@implab/core/di/ActivationContext");
12 15
13 16 export class Container {
14 17 _services: ServiceMap;
15 18
16 19 _cache: object;
17 20
18 21 _cleanup: (() => void)[];
19 22
20 23 _root: Container;
21 24
22 25 _parent: Container;
23 26
24 27 _resolver: ModuleResolverBase;
25 28
26 29 constructor(parent?: Container) {
27 30 this._parent = parent;
28 31 this._services = parent ? Object.create(parent._services) : {};
29 32 this._cache = {};
30 33 this._cleanup = [];
31 34 this._root = parent ? parent.getRootContainer() : this;
32 35 this._services.container = new ValueDescriptor(this);
36 this._resolver = new RequireJsResolver();
33 37 }
34 38
35 39 getRootContainer() {
36 40 return this._root;
37 41 }
38 42
39 43 getParent() {
40 44 return this._parent;
41 45 }
42 46
43 47 resolve(name: string, def?) {
44 48 const d = this._services[name];
45 49 if (d === undefined) {
46 50 if (arguments.length > 1)
47 51 return def;
48 52 else
49 53 throw new Error("Service '" + name + "' isn't found");
50 54 }
51 55
52 56 const context = new ActivationContext(this, this._services);
53 57 try {
54 58 return context.activate(d, name);
55 59 } catch (error) {
56 60 throw new ActivationError(name, context.getStack(), error);
57 61 }
58 62 }
59 63
64 /**
65 * @deprecated use resolve() method
66 */
60 67 getService(name: string, def?) {
61 68 return this.resolve.apply(this, arguments);
62 69 }
63 70
64 71 register(nameOrCollection, service?) {
65 72 if (arguments.length === 1) {
66 73 const data = nameOrCollection;
67 74 for (const name in data)
68 75 this.register(name, data[name]);
69 76 } else {
70 77 if (!isDescriptor(service))
71 78 throw new Error("The service parameter must be a descriptor");
72 79
73 80 this._services[nameOrCollection] = service;
74 81 }
75 82 return this;
76 83 }
77 84
78 85 onDispose(callback) {
79 86 if (!(callback instanceof Function))
80 87 throw new Error("The callback must be a function");
81 88 this._cleanup.push(callback);
82 89 }
83 90
84 91 dispose() {
85 92 if (this._cleanup) {
86 93 for (const f of this._cleanup)
87 94 f();
88 95 this._cleanup = null;
89 96 }
90 97 }
91 98
92 99 /**
93 100 * @param{String|Object} config
94 101 * The configuration of the contaier. Can be either a string or an object,
95 102 * if the configuration is an object it's treated as a collection of
96 103 * services which will be registed in the contaier.
97 104 *
98 105 * @param{Function} opts.contextRequire
99 106 * The function which will be used to load a configuration or types for services.
100 107 *
101 108 */
102 109 async configure(config: string | object, opts?: object) {
103 110 if (typeof (config) === "string") {
111 trace.log(`load configuration '${config}'`);
104 112 const resolver = await this._resolver.createResolver(config, opts);
105 113 const data = await this._resolver.loadModule(config);
106 114 return this._configure(data, { resolver });
107 115 } else {
116 trace.log(`json configuration`);
108 117 return this._configure(config);
109 118 }
110 119 }
111 120
112 121 createChildContainer() {
113 122 return new Container(this);
114 123 }
115 124
116 125 has(id) {
117 126 return id in this._cache;
118 127 }
119 128
120 129 get(id) {
121 130 return this._cache[id];
122 131 }
123 132
124 133 store(id, value) {
125 134 return (this._cache[id] = value);
126 135 }
127 136
128 137 async _configure(data: object, opts?: { resolver: ModuleResolverBase }) {
129 138 const resolver = (opts && opts.resolver) || this._resolver;
130 139
131 140 const services = await this._parseRegistrations(data, resolver);
132 141
133 142 this.register(services);
134 143 }
135 144
136 async _parse(registration: any, resolver: ModuleResolverBase) {
137 if (isPrimitive(registration) || isDescriptor(registration))
138 return registration;
145 async _parse(data: any, resolver: ModuleResolverBase) {
146 if (isPrimitive(data) || isDescriptor(data))
147 return data;
139 148
140 if (isDependencyRegistration(registration)) {
141 return this._paseReference(registration, resolver);
142 } else if (isValueRegistration(registration)) {
143 return !registration.parse ?
144 new ValueDescriptor(registration.$value) :
145 new AggregateDescriptor(this._parse(registration.$value, resolver));
146
147 } else if (registration.$type || registration.$factory) {
148 return this._parseService(registration, resolver);
149 } else if (registration instanceof Array) {
150 return this._parseArray(registration, resolver);
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);
151 157 }
152 158
153 return this._parseObject(registration, resolver);
159 return this._parseObject(data, resolver);
154 160 }
155 161
156 async _paseReference(registration: DependencyRegistration, resolver: ModuleResolverBase) {
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) {
157 169 return new ReferenceDescriptor({
158 170 name: registration.$dependency,
159 171 lazy: registration.lazy,
160 172 optional: registration.optional,
161 173 default: registration.default,
162 174 services: registration.services && await this._parseRegistrations(registration.services, resolver)
163 175 });
164 176 }
165 177
166 async _parseService(data: ServiceRegistration, resolver: ModuleResolverBase) {
178 async _makeServiceDescriptor(data: ServiceRegistration, resolver: ModuleResolverBase) {
167 179 const opts: ServiceDescriptorParams = {
168 180 owner: this
169 181 };
170 182
171 183 if (data.$type) {
172 184 if (data.$type instanceof Function)
173 185 opts.type = data.$type;
174 186 else if (typeof data.$type === "string")
175 187 opts.type = await resolver.resolve(data.$type);
176 188 else
177 189 throw new Error(format("Unsupported type specification: {0:json}", data.$type));
178 190 } else {
179 191 if (data.$factory instanceof Function)
180 192 opts.factory = data.$factory;
181 193 else if (typeof data.$factory === "string")
182 194 opts.factory = await resolver.resolve(data.$factory);
183 195 else
184 196 throw new Error(format("Unsupported factory specification: {0:json}", data.$factory));
185 197 }
186 198
187 199 if (data.services)
188 200 opts.services = await this._parseRegistrations(data.services, resolver);
189 201
190 202 if (data.inject) {
191 203 if (data.inject instanceof Array)
192 204 opts.inject = await Promise.all(data.inject.map(x => this._parseObject(x, resolver)));
193 205 else
194 206 opts.inject = [await this._parseObject(data.inject, resolver)];
195 207 }
196 208
197 209 if (data.params)
198 opts.params = this._parse(data.params, resolver);
210 opts.params = await this._parse(data.params, resolver);
199 211
200 212 if (data.activation) {
201 213 if (typeof (data.activation) === "string") {
202 214 switch (data.activation.toLowerCase()) {
203 215 case "singleton":
204 216 opts.activation = ActivationType.Singleton;
205 217 break;
206 218 case "container":
207 219 opts.activation = ActivationType.Container;
208 220 break;
209 221 case "hierarchy":
210 222 opts.activation = ActivationType.Hierarchy;
211 223 break;
212 224 case "context":
213 225 opts.activation = ActivationType.Context;
214 226 break;
215 227 case "call":
216 228 opts.activation = ActivationType.Call;
217 229 break;
218 230 default:
219 231 throw new Error("Unknown activation type: " +
220 232 data.activation);
221 233 }
222 234 } else {
223 235 opts.activation = Number(data.activation);
224 236 }
225 237 }
226 238
227 239 if (data.cleanup)
228 240 opts.cleanup = data.cleanup;
229 241
230 242 return new ServiceDescriptor(opts);
231 243 }
232 244
233 245 async _parseObject(data: object, resolver: ModuleResolverBase) {
234 246 if (data.constructor &&
235 247 data.constructor.prototype !== Object.prototype)
236 248 return new ValueDescriptor(data);
237 249
238 250 const o = {};
239 251
240 252 for (const p in data)
241 253 o[p] = await this._parse(data[p], resolver);
242 254
243 255 // TODO: handle inline descriptors properly
244 256 // const ex = {
245 257 // activate(ctx) {
246 258 // const value = ctx.activate(this.prop, "prop");
247 259 // // some code
248 260 // },
249 261
250 262 // // will be turned to ReferenceDescriptor
251 263 // prop: { $dependency: "depName" }
252 264 // };
253 265
254 266 return o;
255 267 }
256 268
257 269 async _parseArray(data: Array<any>, resolver: ModuleResolverBase) {
258 270 if (data.constructor &&
259 271 data.constructor.prototype !== Array.prototype)
260 272 return new ValueDescriptor(data);
261 273
262 274 return pmap(data, x => this._parse(x, resolver));
263 275 }
264 276
265 277 async _parseRegistrations(data: object, resolver: ModuleResolverBase) {
266 278 if (data.constructor &&
267 279 data.constructor.prototype !== Object.prototype)
268 280 throw new Error("Registrations must be a simple object");
269 281
270 282 const o: ServiceMap = {};
271 283
272 284 for (const p of Object.keys(data)) {
273 285 const v = await this._parse(data[p], resolver);
274 286 o[p] = isDescriptor(v) ? v : new AggregateDescriptor(v);
275 287 }
276 288
277 289 return o;
278 290 }
279 291 }
@@ -1,74 +1,100
1 1 import { ModuleResolverBase } from "./ModuleResolverBase";
2 2 import { Uuid } from "../Uuid";
3 3 import { argumentNotEmptyString } from "../safe";
4 4 import { TraceSource } from "../log/TraceSource";
5 5
6 declare function require(modules: string[], cb?: (...args: any[]) => any): void;
6 type RequireFn = (modules: string[], cb?: (...args: any[]) => any) => void;
7 7
8 declare function define(name: string, modules: string[], cb?: (...args: any[]) => any): void;
8 declare const require: RequireFn;
9
10 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;
9 12
10 13 interface RequireJsResolverParams {
11 14 contextRequire: (modules: string[], cb?: (...args: any[]) => any) => void;
12 15
13 16 base: string;
14 17 }
15 18
16 TraceSource.get("RequireJsResolver");
19 const trace = TraceSource.get("@implab/core/di/RequireJsResolver");
17 20
18 21 export class RequireJsResolver extends ModuleResolverBase {
19 22 _contextRequire = require;
20 23
21 24 _base: string;
22 25
23 constructor(opts) {
26 constructor(opts?: RequireJsResolverParams) {
24 27 super();
25 28
26 29 if (opts) {
27 30
28 31 if (opts.contextRequire)
29 32 this._contextRequire = opts.contextRequire;
30 33
31 34 if (opts.base) {
32 35 if (opts.base.indexOf("./") === 0)
33 36 throw new Error(`A module id should be an absolute: '${opts.base}'`);
34 37 this._base = opts.base;
35 38 }
36 39 }
37 40
38 41 }
39 42
40 43 async createResolver(moduleName: string): Promise<ModuleResolverBase> {
41 44 argumentNotEmptyString(moduleName, "moduleName");
42 45
46 trace.log("createResolver({0})", moduleName);
47
43 48 const parts = moduleName.split("/");
44 49 if (parts[0] === ".") {
45 50 if (this._base)
46 51 parts[0] = this._base;
47 52 else
48 53 throw new Error(`Can't resolve a relative module '${moduleName}'`);
49 54 }
50 55
51 56 if (parts.length > 1)
52 57 parts.splice(-1, 1, Uuid());
53 58 else
54 59 parts.push(Uuid());
55 60
56 61 const shim = parts.join("/");
57 62
58 const contextRequire = await new Promise(
59 resolve => define(shim, ["require"], resolve)
60 );
63 trace.debug(`define shim ${shim}`);
61 64
62 return new RequireJsResolver({
63 base: parts.slice(0, -1).join("/"),
64 contextRequire
65 });
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
66 91 }
67 92
68 93 async loadModule(moduleName: string): Promise<object> {
94 trace.log(`loadModule(${moduleName})`);
69 95 return new Promise<object>(
70 96 resolve => this._contextRequire.call(null, [moduleName], resolve)
71 97 );
72 98 }
73 99
74 100 }
@@ -1,286 +1,288
1 1 import { ActivationContext } from "./ActivationContext";
2 2 import { Descriptor, ActivationType, ServiceMap, isDescriptor } from "./interfaces";
3 3 import { Container } from "./Container";
4 4 import { argumentNotNull, isPrimitive, oid, isPromise } from "../safe";
5 5 import { Constructor, Factory } from "../interfaces";
6 6 import { TraceSource } from "../log/TraceSource";
7 7
8 8 let cacheId = 0;
9 9
10 10 const trace = TraceSource.get("@implab/core/di/ActivationContext");
11 11
12 12 function injectMethod(target, method, context, args) {
13 13 const m = target[method];
14 14 if (!m)
15 15 throw new Error("Method '" + method + "' not found");
16 16
17 17 if (args instanceof Array)
18 18 return m.apply(target, context.parse(args, "." + method));
19 19 else
20 20 return m.call(target, context.parse(args, "." + method));
21 21 }
22 22
23 23 function makeClenupCallback(target, method: ((instance) => void) | string) {
24 24 if (typeof (method) === "string") {
25 25 return () => {
26 26 target[method]();
27 27 };
28 28 } else {
29 29 return () => {
30 30 method(target);
31 31 };
32 32 }
33 33 }
34 34
35 35 // TODO: make async
36 36 function _parse(value, context: ActivationContext, path: string) {
37 37 if (isPrimitive(value))
38 38 return value;
39 39
40 trace.debug("parse {0}", path);
41
40 42 if (isDescriptor(value))
41 43 return context.activate(value, path);
42 44
43 45 if (value instanceof Array)
44 return value.map((x, i) => this._parse(x, context, `${path}[${i}]`));
46 return value.map((x, i) => _parse(x, context, `${path}[${i}]`));
45 47
46 48 const t = {};
47 49 for (const p of Object.keys(value))
48 t[p] = this._parse(value[p], context, `${path}.${p}`);
50 t[p] = _parse(value[p], context, `${path}.${p}`);
51
49 52 return t;
50
51 53 }
52 54
53 55 export interface ServiceDescriptorParams {
54 56 activation?: ActivationType;
55 57
56 58 owner: Container;
57 59
58 60 type?: Constructor;
59 61
60 62 factory?: Factory;
61 63
62 64 params?;
63 65
64 66 inject?: object[];
65 67
66 68 services?: ServiceMap;
67 69
68 70 cleanup?: ((x) => void) | string;
69 71 }
70 72
71 73 export class ServiceDescriptor implements Descriptor {
72 74 _instance;
73 75
74 76 _hasInstance = false;
75 77
76 78 _activationType = ActivationType.Call;
77 79
78 80 _services: ServiceMap;
79 81
80 82 _type: Constructor = null;
81 83
82 84 _factory: Factory = null;
83 85
84 86 _params;
85 87
86 88 _inject: object[];
87 89
88 90 _cleanup: ((x) => void) | string;
89 91
90 92 _cacheId: any;
91 93
92 94 _owner: Container;
93 95
94 96 constructor(opts: ServiceDescriptorParams) {
95 97 argumentNotNull(opts, "opts");
96 98 argumentNotNull(opts.owner, "owner");
97 99
98 100 this._owner = opts.owner;
99 101
100 102 if (!(opts.type || opts.factory))
101 103 throw new Error(
102 104 "Either a type or a factory must be specified");
103 105
104 106 if (opts.activation)
105 107 this._activationType = opts.activation;
106 108
107 109 if (opts.type)
108 110 this._type = opts.type;
109 111
110 112 if (opts.params)
111 113 this._params = opts.params;
112 114
113 115 if (opts.inject)
114 116 this._inject = opts.inject;
115 117
116 118 if (opts.services)
117 119 this._services = opts.services;
118 120
119 121 if (opts.factory)
120 122 this._factory = opts.factory;
121 123
122 124 if (opts.cleanup) {
123 125 if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function))
124 126 throw new Error(
125 127 "The cleanup parameter must be either a function or a function name");
126 128
127 129 this._cleanup = opts.cleanup;
128 130 }
129 131
130 132 if (this._activationType === ActivationType.Singleton) {
131 133 const tof = this._type || this._factory;
132 134
133 135 // create the persistent cache identifier for the type
134 136 if (isPrimitive(tof))
135 137 this._cacheId = tof;
136 138 else
137 139 this._cacheId = oid(tof);
138 140 } else {
139 141 this._cacheId = ++cacheId;
140 142 }
141 143 }
142 144
143 145 activate(context: ActivationContext) {
144 146 // if we have a local service records, register them first
145 147 let instance;
146 148
147 149 switch (this._activationType) {
148 150 case ActivationType.Singleton: // SINGLETON
149 151 // if the value is cached return it
150 152 if (this._hasInstance)
151 153 return this._instance;
152 154
153 155 // singletons are bound to the root container
154 156 const container = context.container.getRootContainer();
155 157
156 158 if (container.has(this._cacheId)) {
157 159 instance = container.get(this._cacheId);
158 160 } else {
159 161 instance = this._create(context);
160 162 container.store(this._cacheId, instance);
161 163 if (this._cleanup)
162 164 container.onDispose(
163 165 makeClenupCallback(instance, this._cleanup));
164 166 }
165 167
166 168 this._hasInstance = true;
167 169 return (this._instance = instance);
168 170
169 171 case ActivationType.Container: // CONTAINER
170 172 // return a cached value
171 173
172 174 if (this._hasInstance)
173 175 return this._instance;
174 176
175 177 // create an instance
176 178 instance = this._create(context);
177 179
178 180 // the instance is bound to the container
179 181 if (this._cleanup)
180 182 this._owner.onDispose(
181 183 makeClenupCallback(instance, this._cleanup));
182 184
183 185 // cache and return the instance
184 186 this._hasInstance = true;
185 187 return (this._instance = instance);
186 188 case ActivationType.Context: // CONTEXT
187 189 // return a cached value if one exists
188 190
189 191 if (context.has(this._cacheId))
190 192 return context.get(this._cacheId);
191 193 // context context activated instances are controlled by callers
192 194 return context.store(this._cacheId, this._create(context));
193 195 case ActivationType.Call: // CALL
194 196 // per-call created instances are controlled by callers
195 197 return this._create(context);
196 198 case ActivationType.Hierarchy: // HIERARCHY
197 199 // hierarchy activated instances are behave much like container activated
198 200 // except they are created and bound to the child container
199 201
200 202 // return a cached value
201 203 if (context.container.has(this._cacheId))
202 204 return context.container.get(this._cacheId);
203 205
204 206 instance = this._create(context);
205 207
206 208 if (this._cleanup)
207 209 context.container.onDispose(makeClenupCallback(
208 210 instance,
209 211 this._cleanup));
210 212
211 213 return context.container.store(this._cacheId, instance);
212 214 default:
213 215 throw new Error("Invalid activation type: " + this._activationType);
214 216 }
215 217 }
216 218
217 219 isInstanceCreated() {
218 220 return this._hasInstance;
219 221 }
220 222
221 223 getInstance() {
222 224 return this._instance;
223 225 }
224 226
225 227 _create(context: ActivationContext) {
226 228 trace.debug(`constructing ${context._name}`);
227 229
228 230 if (this._activationType !== ActivationType.Call &&
229 231 context.visit(this._cacheId) > 0)
230 232 throw new Error("Recursion detected");
231 233
232 234 if (this._services) {
233 235 for (const p in this._services)
234 236 context.register(p, this._services[p]);
235 237 }
236 238
237 239 let instance;
238 240
239 241 if (!this._factory) {
240 242 const ctor = this._type;
241 243 if (this._params && this._params.length) {
242 244 this._factory = (...args) => {
243 245 const t = Object.create(ctor.prototype);
244 246 const inst = ctor.apply(t, args);
245 247 return isPrimitive(inst) ? t : inst;
246 248 };
247 249 } else {
248 250 this._factory = () => {
249 251 return new ctor();
250 252 };
251 253 }
252 254 }
253 255
254 256 if (this._params === undefined) {
255 257 instance = this._factory();
256 258 } else if (this._params instanceof Array) {
257 259 instance = this._factory.apply(this, _parse(this._params, context, "args"));
258 260 } else {
259 261 instance = this._factory(_parse(this._params, context, "args"));
260 262 }
261 263
262 264 if (this._inject) {
263 265 this._inject.forEach(spec => {
264 266 for (const m in spec)
265 267 injectMethod(instance, m, context, spec[m]);
266 268 });
267 269 }
268 270
269 271 return instance;
270 272 }
271 273
272 274 // @constructor {singleton} foo/bar/Baz
273 275 // @factory {singleton}
274 276 toString() {
275 277 const parts = [];
276 278
277 279 parts.push(this._type ? "@constructor" : "@factory");
278 280
279 281 parts.push(ActivationType[this._activationType]);
280 282
281 283 if (typeof (this._type) === "string")
282 284 parts.push(this._type);
283 285
284 286 return parts.join(" ");
285 287 }
286 288 }
@@ -1,66 +1,66
1 1 import { isNull, isPrimitive } from "../safe";
2 2 import { ActivationContext } from "./ActivationContext";
3 3 import { Constructor, Factory } from "../interfaces";
4 4
5 5 export interface Descriptor {
6 6 activate(context: ActivationContext, name?: string);
7 7 }
8 8
9 9 export function isDescriptor(x): x is Descriptor {
10 10 return (!isPrimitive(x)) &&
11 11 (x.activate instanceof Function);
12 12 }
13 13
14 14 export interface ServiceMap {
15 15 [s: string]: Descriptor;
16 16 }
17 17
18 18 export enum ActivationType {
19 19 Singleton,
20 20 Container,
21 21 Hierarchy,
22 22 Context,
23 23 Call
24 24 }
25 25
26 26 export interface RegistrationWithServices {
27 27 services?: object;
28 28 }
29 29
30 30 export interface ServiceRegistration extends RegistrationWithServices {
31 31 $type?: string | Constructor;
32 32
33 33 $factory?: string | Factory;
34 34
35 35 activation?: "singleton" | "container" | "hierarchy" | "context" | "call";
36 36
37 37 params?;
38 38
39 39 inject?: object | object[];
40 40
41 41 cleanup: (instance) => void | string;
42 42 }
43 43
44 44 export interface ValueRegistration {
45 45 $value;
46 46 parse?: boolean;
47 47 }
48 48
49 49 export interface DependencyRegistration extends RegistrationWithServices {
50 50 $dependency: string;
51 51 lazy?: boolean;
52 52 optional?: boolean;
53 53 default?;
54 54 }
55 55
56 56 export function isServiceRegistration(x): x is ServiceRegistration {
57 57 return (!isPrimitive(x)) && ("$type" in x || "$factory" in x);
58 58 }
59 59
60 60 export function isValueRegistration(x): x is ValueRegistration {
61 61 return (!isPrimitive(x)) && ("$value" in x);
62 62 }
63 63
64 64 export function isDependencyRegistration(x): x is DependencyRegistration {
65 return (!isPrimitive(x)) && ("$depdendency" in x);
65 return (!isPrimitive(x)) && ("$dependency" in x);
66 66 }
@@ -1,188 +1,187
1 import * as format from '../text/format'
2 import { argumentNotNull } from '../safe';
3 import { Observable } from '../Observable'
4 import { IDestroyable } from '../interfaces';
1 import * as format from "../text/format";
2 import { argumentNotNull } from "../safe";
3 import { Observable } from "../Observable";
4 import { IDestroyable } from "../interfaces";
5 5
6 6 export const DebugLevel = 400;
7 7
8 8 export const LogLevel = 300;
9 9
10 10 export const WarnLevel = 200;
11 11
12 12 export const ErrorLevel = 100;
13 13
14 14 export const SilentLevel = 0;
15 15
16 16 export class TraceEvent {
17 17 readonly source: TraceSource;
18 18
19 readonly level: Number;
19 readonly level: number;
20 20
21 21 readonly arg: any;
22 22
23 constructor(source: TraceSource, level: Number, arg: any) {
23 constructor(source: TraceSource, level: number, arg: any) {
24 24 this.source = source;
25 25 this.level = level;
26 26 this.arg = arg;
27 27 }
28 28 }
29 29
30 30 class Registry {
31 31 static readonly instance = new Registry();
32 32
33 33 private _registry: object = new Object();
34 34 private _listeners: object = new Object();
35 35 private _nextCookie: number = 1;
36 36
37 37 get(id: any): TraceSource {
38 38 argumentNotNull(id, "id");
39 39
40 40 if (this._registry[id])
41 41 return this._registry[id];
42 42
43 43 var source = new TraceSource(id);
44 44 this._registry[id] = source;
45 45 this._onNewSource(source);
46 46
47 47 return source;
48 48 }
49 49
50 50 add(id: any, source: TraceSource) {
51 51 argumentNotNull(id, "id");
52 52 argumentNotNull(source, "source");
53 53
54 54 this._registry[id] = source;
55 55 this._onNewSource(source);
56 56 }
57 57
58 58 _onNewSource(source: TraceSource) {
59 59 for (let i in this._listeners)
60 60 this._listeners[i].call(null, source);
61 61 }
62 62
63 63 on(handler: (source: TraceSource) => void): IDestroyable {
64 64 argumentNotNull(handler, "handler");
65 65 var me = this;
66 66
67 67 var cookie = this._nextCookie++;
68 68
69 69 this._listeners[cookie] = handler;
70 70
71 71 for (let i in this._registry)
72 72 handler(this._registry[i]);
73 73
74 74 return {
75 75 destroy() {
76 76 delete me._listeners[cookie];
77 77 }
78 78 };
79 79 }
80 80 }
81 81
82 82 export class TraceSource {
83 83 readonly id: any
84 84
85 85 level: number
86 86
87 87 readonly events: Observable<TraceEvent>
88 88
89 89 _notifyNext: (arg: TraceEvent) => void
90 90
91 91 constructor(id: any) {
92 92
93 93 this.id = id || new Object();
94 94 this.events = new Observable((next) => {
95 95 this._notifyNext = next;
96 96 })
97 97 }
98 98
99 99 protected emit(level: number, arg: any) {
100 100 this._notifyNext(new TraceEvent(this, level, arg));
101 101 }
102 102
103 103 isDebugEnabled() {
104 104 return this.level >= DebugLevel;
105 105 }
106 106
107 107 debug(msg: string, ...args: any[]) {
108 108 if (this.isEnabled(DebugLevel))
109 109 this.emit(DebugLevel, format.apply(null, arguments));
110 110 }
111 111
112 112 isLogEnabled() {
113 113 return this.level >= LogLevel;
114 114 }
115 115
116 116 log(msg: string, ...args: any[]) {
117 117 if (this.isEnabled(LogLevel))
118 118 this.emit(LogLevel, format.apply(null, arguments));
119 119 }
120 120
121 121 isWarnEnabled() {
122 122 return this.level >= WarnLevel;
123 123 }
124 124
125 125 warn(msg: string, ...args: any[]) {
126 126 if (this.isEnabled(WarnLevel))
127 127 this.emit(WarnLevel, format.apply(null, arguments));
128 128 }
129 129
130 130 /**
131 131 * returns true if errors will be recorded.
132 132 */
133 133 isErrorEnabled() {
134 134 return this.level >= ErrorLevel;
135 135 }
136 136
137 137 /**
138 138 * Traces a error.
139 139 *
140 140 * @param msg the message.
141 141 * @param args parameters which will be substituted in the message.
142 142 */
143 143 error(msg: string, ...args: any[]) {
144 144 if (this.isEnabled(ErrorLevel))
145 145 this.emit(ErrorLevel, format.apply(null, arguments));
146 146 }
147 147
148 148 /**
149 149 * Checks whether the specified level is enabled for this
150 150 * trace source.
151 151 *
152 152 * @param level the trace level which should be checked.
153 153 */
154 154 isEnabled(level: number) {
155 155 return (this.level >= level);
156 156 }
157 157
158 158 /**
159 159 * Traces a raw event, passing data as it is to the underlying listeners
160 160 *
161 161 * @param level the level of the event
162 162 * @param arg the data of the event, can be a simple string or any object.
163 163 */
164 164 traceEvent(level: number, arg: any) {
165 165 if (this.isEnabled(level))
166 166 this.emit(level, arg);
167 167 }
168 168
169 169 /**
170 170 * Register the specified handler to be called for every new and already
171 171 * created trace source.
172 172 *
173 173 * @param handler the handler which will be called for each trace source
174 174 */
175 175 static on(handler: (source: TraceSource) => void) {
176 176 return Registry.instance.on(handler);
177 177 }
178 178
179 179 /**
180 180 * Creates or returns already created trace source for the specified id.
181 181 *
182 182 * @param id the id for the trace source
183 183 */
184 184 static get(id: any) {
185 185 return Registry.instance.get(id);
186 186 }
187 187 }
188
@@ -1,74 +1,118
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 12 const writer = new TapeWriter(t);
13 13
14 14 TraceSource.on(ts => {
15 15 ts.level = DebugLevel;
16 16 writer.writeEvents(ts.events);
17 17 });
18 18
19 19 const container = new Container();
20 20
21 21 const connection1 = "db://localhost";
22 22
23 container.register("connection", new ValueDescriptor(connection1));
23 t.throws(
24 () => container.register("bla-bla", "bla-bla"),
25 "Do not allow to register anything other than descriptors"
26 );
24 27
25 t.equals(container.getService("connection"), connection1);
28 t.doesNotThrow(
29 () => container.register("connection", new ValueDescriptor(connection1)),
30 "register ValueDescriptor"
31 );
26 32
27 container.register(
28 "dbParams",
29 new AggregateDescriptor({
30 timeout: 10,
31 connection: new ReferenceDescriptor({ name: "connection" })
32 })
33 t.equals(container.getService("connection"), connection1, "resolve string value");
34
35 t.doesNotThrow(
36 () => container.register(
37 "dbParams",
38 new AggregateDescriptor({
39 timeout: 10,
40 connection: new ReferenceDescriptor({ name: "connection" })
41 })
42 ),
43 "register AggregateDescriptor"
33 44 );
34 45
35 46 const dbParams = container.getService("dbParams");
36 t.equals(dbParams.connection, connection1, "should get connection");
47 t.equals(dbParams.connection, connection1, "should get string value 'dbParams.connection'");
37 48
38 49 writer.destroy();
39 50 });
40 51
41 52 test("Container configure/resolve tests", async t => {
42 53 const writer = new TapeWriter(t);
43 54
44 55 TraceSource.on(ts => {
45 56 ts.level = DebugLevel;
46 57 writer.writeEvents(ts.events);
47 58 });
48 59
49 60 const container = new Container();
50 61
51 62 await container.configure({
52 63 foo: {
53 64 $type: Foo
54 65 },
55 66
67 box: {
68 $type: Bar,
69 params: {
70 $dependency: "foo"
71 }
72 },
73
56 74 bar: {
57 75 $type: Bar,
58 76 params: {
59 77 db: {
60 78 provider: {
61 79 $dependency: "db"
62 80 }
63 81 }
64 82 }
65 83 }
66 84 });
85 t.pass("should configure from js object");
67 86
68 87 const f1 = container.resolve("foo");
88
89 t.assert(!isNull(f1), "foo should be not null");
90
91 t.throws(() => container.resolve("bar"), "should not resolve dependency 'db'");
92
93 writer.destroy();
94 });
95
96 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();
105
106 await container.configure("test/mock/config1");
107 t.pass("The configuration should load");
108
109 const f1 = container.resolve("foo");
110
69 111 t.assert(!isNull(f1), "foo should be not null");
70 112
71 113 const b1 = container.resolve("bar");
72 114
115 t.assert(!isNull(b1), "foo should be not null");
116
73 117 writer.destroy();
74 118 });
General Comments 0
You need to be logged in to leave comments. Login now