| @@ -1,4 +1,4 | |||
|
|
1 |
Copyright 2017-201 |
|
|
|
1 | Copyright 2017-2019 Implab team | |
|
|
2 | 2 | |
|
|
3 | 3 | Redistribution and use in source and binary forms, with or without |
|
|
4 | 4 | modification, are permitted provided that the following conditions are met: |
| @@ -83,7 +83,7 | |||
|
|
83 | 83 | }, |
|
|
84 | 84 | "duplexer": { |
|
|
85 | 85 | "version": "0.1.1", |
|
|
86 | "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", | |
|
|
86 | "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", | |
|
|
87 | 87 | "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", |
|
|
88 | 88 | "dev": true |
|
|
89 | 89 | }, |
| @@ -128,7 +128,7 | |||
|
|
128 | 128 | "dependencies": { |
|
|
129 | 129 | "tape": { |
|
|
130 | 130 | "version": "2.3.3", |
|
|
131 | "resolved": "http://registry.npmjs.org/tape/-/tape-2.3.3.tgz", | |
|
|
131 | "resolved": "https://registry.npmjs.org/tape/-/tape-2.3.3.tgz", | |
|
|
132 | 132 | "integrity": "sha1-Lnzgox3wn41oUWZKcYQuDKUFevc=", |
|
|
133 | 133 | "dev": true, |
|
|
134 | 134 | "requires": { |
| @@ -288,7 +288,7 | |||
|
|
288 | 288 | }, |
|
|
289 | 289 | "path-is-absolute": { |
|
|
290 | 290 | "version": "1.0.1", |
|
|
291 | "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", | |
|
|
291 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", | |
|
|
292 | 292 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", |
|
|
293 | 293 | "dev": true |
|
|
294 | 294 | }, |
| @@ -300,7 +300,7 | |||
|
|
300 | 300 | }, |
|
|
301 | 301 | "readable-stream": { |
|
|
302 | 302 | "version": "1.1.14", |
|
|
303 | "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", | |
|
|
303 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", | |
|
|
304 | 304 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", |
|
|
305 | 305 | "dev": true, |
|
|
306 | 306 | "requires": { |
| @@ -353,7 +353,7 | |||
|
|
353 | 353 | }, |
|
|
354 | 354 | "string_decoder": { |
|
|
355 | 355 | "version": "0.10.31", |
|
|
356 | "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", | |
|
|
356 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", | |
|
|
357 | 357 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", |
|
|
358 | 358 | "dev": true |
|
|
359 | 359 | }, |
| @@ -410,13 +410,13 | |||
|
|
410 | 410 | }, |
|
|
411 | 411 | "through": { |
|
|
412 | 412 | "version": "2.3.8", |
|
|
413 | "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", | |
|
|
413 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", | |
|
|
414 | 414 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", |
|
|
415 | 415 | "dev": true |
|
|
416 | 416 | }, |
|
|
417 | 417 | "through2": { |
|
|
418 | 418 | "version": "0.2.3", |
|
|
419 | "resolved": "http://registry.npmjs.org/through2/-/through2-0.2.3.tgz", | |
|
|
419 | "resolved": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz", | |
|
|
420 | 420 | "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=", |
|
|
421 | 421 | "dev": true, |
|
|
422 | 422 | "requires": { |
| @@ -8,7 +8,7 const trace = TraceSource.get("@implab/c | |||
|
|
8 | 8 | export interface ActivationContextInfo { |
|
|
9 | 9 | name: string; |
|
|
10 | 10 | |
|
|
11 |
service: |
|
|
|
11 | service: string; | |
|
|
12 | 12 | |
|
|
13 | 13 | scope: ServiceMap; |
|
|
14 | 14 | } |
| @@ -22,12 +22,17 export class ActivationContext { | |||
|
|
22 | 22 | |
|
|
23 | 23 | _visited: object; |
|
|
24 | 24 | |
|
|
25 | _name: string; | |
|
|
26 | ||
|
|
27 | _localized: boolean; | |
|
|
28 | ||
|
|
25 | 29 | container: Container; |
|
|
26 | 30 | |
|
|
27 | constructor(container: Container, services: ServiceMap, cache?: object, visited?) { | |
|
|
31 | constructor(container: Container, services: ServiceMap, name?: string, cache?: object, visited?) { | |
|
|
28 | 32 | argumentNotNull(container, "container"); |
|
|
29 | 33 | argumentNotNull(services, "services"); |
|
|
30 | 34 | |
|
|
35 | this._name = name; | |
|
|
31 | 36 | this._visited = visited || {}; |
|
|
32 | 37 | this._stack = []; |
|
|
33 | 38 | this._cache = cache || {}; |
| @@ -35,7 +40,11 export class ActivationContext { | |||
|
|
35 | 40 | this.container = container; |
|
|
36 | 41 | } |
|
|
37 | 42 | |
|
|
38 | getService(name, def?): any { | |
|
|
43 | getName() { | |
|
|
44 | return this._name; | |
|
|
45 | } | |
|
|
46 | ||
|
|
47 | resolve(name, def?): any { | |
|
|
39 | 48 | const d = this._services[name]; |
|
|
40 | 49 | |
|
|
41 | 50 | if (!d) |
| @@ -44,7 +53,7 export class ActivationContext { | |||
|
|
44 | 53 | else |
|
|
45 | 54 | throw new Error(`Service ${name} not found`); |
|
|
46 | 55 | |
|
|
47 |
return is |
|
|
|
56 | return this.activate(d, name); | |
|
|
48 | 57 | } |
|
|
49 | 58 | |
|
|
50 | 59 | /** |
| @@ -62,7 +71,8 export class ActivationContext { | |||
|
|
62 | 71 | clone() { |
|
|
63 | 72 | return new ActivationContext( |
|
|
64 | 73 | this.container, |
|
|
65 |
|
|
|
|
74 | this._services, | |
|
|
75 | this._name, | |
|
|
66 | 76 | this._cache, |
|
|
67 | 77 | this._visited |
|
|
68 | 78 | ); |
| @@ -80,25 +90,18 export class ActivationContext { | |||
|
|
80 | 90 | return (this._cache[id] = value); |
|
|
81 | 91 | } |
|
|
82 | 92 | |
|
|
83 |
|
|
|
|
84 | if (isPrimitive(data)) | |
|
|
85 | return data; | |
|
|
93 | activate(d: Descriptor, name: string) { | |
|
|
94 | if (trace.isLogEnabled()) | |
|
|
95 | trace.log(`enter ${name} ${d}`); | |
|
|
86 | 96 | |
|
|
87 | if (isDescriptor(data)) { | |
|
|
88 |
|
|
|
|
89 | } else if (data instanceof Array) { | |
|
|
90 | this.enter(name); | |
|
|
91 | const v = data.map( (x, i) => this.parse(x, `[${i}]`)); | |
|
|
92 |
t |
|
|
|
93 | return v; | |
|
|
94 | } else { | |
|
|
95 | this.enter(name); | |
|
|
96 | const result = {}; | |
|
|
97 | for (const p in data) | |
|
|
98 | result[p] = this.parse(data[p], "." + p); | |
|
|
99 | this.leave(); | |
|
|
100 | return result; | |
|
|
101 | } | |
|
|
97 | this.enter(name, d.toString()); | |
|
|
98 | const v = d.activate(this); | |
|
|
99 | this.leave(); | |
|
|
100 | ||
|
|
101 | if (trace.isLogEnabled()) | |
|
|
102 | trace.log(`leave ${name}`); | |
|
|
103 | ||
|
|
104 | return v; | |
|
|
102 | 105 | } |
|
|
103 | 106 | |
|
|
104 | 107 | visit(id: string) { |
| @@ -111,24 +114,19 export class ActivationContext { | |||
|
|
111 | 114 | return this._stack.slice().reverse(); |
|
|
112 | 115 | } |
|
|
113 | 116 | |
|
|
114 | enter(name: string, d?: Descriptor, localize?: boolean) { | |
|
|
115 | if (trace.isLogEnabled()) | |
|
|
116 | trace.log("enter " + name + " " + (d || "") + | |
|
|
117 | (localize ? " localize" : "")); | |
|
|
117 | private enter(name: string, service: string) { | |
|
|
118 | 118 | this._stack.push({ |
|
|
119 | 119 | name, |
|
|
120 |
service |
|
|
|
120 | service, | |
|
|
121 | 121 | scope: this._services |
|
|
122 | 122 | }); |
|
|
123 | if (localize) | |
|
|
124 |
|
|
|
|
123 | this._name = name; | |
|
|
124 | this._services = Object.create(this._services); | |
|
|
125 | 125 | } |
|
|
126 | 126 | |
|
|
127 | leave() { | |
|
|
127 | private leave() { | |
|
|
128 | 128 | const ctx = this._stack.pop(); |
|
|
129 | 129 | this._services = ctx.scope; |
|
|
130 | ||
|
|
131 | if (trace.isLogEnabled()) | |
|
|
132 | trace.log("leave " + ctx.name + " " + (ctx.service || "")); | |
|
|
130 | this._name = ctx.name; | |
|
|
133 | 131 | } |
|
|
134 | 132 | } |
| @@ -27,7 +27,7 export class ActivationError { | |||
|
|
27 | 27 | if (this.activationStack) { |
|
|
28 | 28 | parts.push("at"); |
|
|
29 | 29 | this.activationStack |
|
|
30 |
.forEach(x => parts.push(` ${x.name} ${x.service |
|
|
|
30 | .forEach(x => parts.push(` ${x.name} ${x.service}`)); | |
|
|
31 | 31 | |
|
|
32 | 32 | } |
|
|
33 | 33 | |
| @@ -1,5 +1,6 | |||
|
|
1 | import { Descriptor } from "./interfaces"; | |
|
|
1 | import { Descriptor, isDescriptor } from "./interfaces"; | |
|
|
2 | 2 | import { ActivationContext } from "./ActivationContext"; |
|
|
3 | import { isPrimitive } from "util"; | |
|
|
3 | 4 | |
|
|
4 | 5 | export class AggregateDescriptor implements Descriptor { |
|
|
5 | 6 | _value: object; |
| @@ -8,17 +9,29 export class AggregateDescriptor impleme | |||
|
|
8 | 9 | this._value = value; |
|
|
9 | 10 | } |
|
|
10 | 11 | |
|
|
11 |
activate(context: ActivationContext |
|
|
|
12 | context.enter(name); | |
|
|
13 | const v = context.parse(this._value, ".params"); | |
|
|
14 | context.leave(); | |
|
|
15 | return v; | |
|
|
12 | activate(context: ActivationContext) { | |
|
|
13 | return this._parse(this._value, context, "$value"); | |
|
|
16 | 14 | } |
|
|
17 | 15 | |
|
|
18 | isInstanceCreated(): boolean { | |
|
|
19 | return false; | |
|
|
16 | // TODO: make async | |
|
|
17 | _parse(value, context: ActivationContext, path: string) { | |
|
|
18 | if (isPrimitive(value)) | |
|
|
19 | return value; | |
|
|
20 | ||
|
|
21 | if (isDescriptor(value)) | |
|
|
22 | return context.activate(value, path); | |
|
|
23 | ||
|
|
24 | if (value instanceof Array) | |
|
|
25 | return value.map((x, i) => this._parse(x, context, `${path}[${i}]`)); | |
|
|
26 | ||
|
|
27 | const t = {}; | |
|
|
28 | for (const p of Object.keys(value)) | |
|
|
29 | t[p] = this._parse(value[p], context, `${path}.${p}`); | |
|
|
30 | return t; | |
|
|
31 | ||
|
|
20 | 32 | } |
|
|
21 | getInstance(): any { | |
|
|
22 | throw new Error("Not supported"); | |
|
|
33 | ||
|
|
34 | toString() { | |
|
|
35 | return "@walk"; | |
|
|
23 | 36 | } |
|
|
24 | 37 | } |
| @@ -1,13 +1,14 | |||
|
|
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 } from "./interfaces"; | |
|
|
4 | import { isDescriptor, ActivationType, ServiceMap, isDependencyRegistration, isValueRegistration, ServiceRegistration, DependencyRegistration } from "./interfaces"; | |
|
|
5 | 5 | import { AggregateDescriptor } from "./AggregateDescriptor"; |
|
|
6 | import { isPrimitive } from "../safe"; | |
|
|
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 | 12 | |
|
|
12 | 13 | export class Container { |
|
|
13 | 14 | _services: ServiceMap; |
| @@ -39,35 +40,36 export class Container { | |||
|
|
39 | 40 | return this._parent; |
|
|
40 | 41 | } |
|
|
41 | 42 | |
|
|
42 |
|
|
|
|
43 | resolve(name: string, def?) { | |
|
|
43 | 44 | const d = this._services[name]; |
|
|
44 |
if ( |
|
|
|
45 | if (d === undefined) { | |
|
|
45 | 46 | if (arguments.length > 1) |
|
|
46 | 47 | return def; |
|
|
47 | 48 | else |
|
|
48 | 49 | throw new Error("Service '" + name + "' isn't found"); |
|
|
49 | ||
|
|
50 | if (!isDescriptor(d)) | |
|
|
51 | return d; | |
|
|
52 | ||
|
|
53 | if (d.isInstanceCreated()) | |
|
|
54 | return d.getInstance(); | |
|
|
50 | } | |
|
|
55 | 51 | |
|
|
56 | 52 | const context = new ActivationContext(this, this._services); |
|
|
57 | ||
|
|
58 | 53 | try { |
|
|
59 |
return |
|
|
|
54 | return context.activate(d, name); | |
|
|
60 | 55 | } catch (error) { |
|
|
61 | 56 | throw new ActivationError(name, context.getStack(), error); |
|
|
62 | 57 | } |
|
|
63 | 58 | } |
|
|
64 | 59 | |
|
|
60 | getService(name: string, def?) { | |
|
|
61 | return this.resolve.apply(this, arguments); | |
|
|
62 | } | |
|
|
63 | ||
|
|
65 | 64 | register(nameOrCollection, service?) { |
|
|
66 | 65 | if (arguments.length === 1) { |
|
|
67 | 66 | const data = nameOrCollection; |
|
|
68 | 67 | for (const name in data) |
|
|
69 | 68 | this.register(name, data[name]); |
|
|
70 | 69 | } else { |
|
|
70 | if (!isDescriptor(service)) | |
|
|
71 | throw new Error("The service parameter must be a descriptor"); | |
|
|
72 | ||
|
|
71 | 73 | this._services[nameOrCollection] = service; |
|
|
72 | 74 | } |
|
|
73 | 75 | return this; |
| @@ -126,19 +128,7 export class Container { | |||
|
|
126 | 128 | async _configure(data: object, opts?: { resolver: ModuleResolverBase }) { |
|
|
127 | 129 | const resolver = (opts && opts.resolver) || this._resolver; |
|
|
128 | 130 | |
|
|
129 | const services: ServiceMap = {}; | |
|
|
130 | ||
|
|
131 | resolver.beginBatch(); | |
|
|
132 | ||
|
|
133 | async function parse(k) { | |
|
|
134 | services[k] = await this._parse(data[k], resolver); | |
|
|
135 | } | |
|
|
136 | ||
|
|
137 | const batch = Object.keys(data).map(parse); | |
|
|
138 | ||
|
|
139 | resolver.completeBatch(); | |
|
|
140 | ||
|
|
141 | await Promise.all(batch); | |
|
|
131 | const services = await this._parseRegistrations(data, resolver); | |
|
|
142 | 132 | |
|
|
143 | 133 | this.register(services); |
|
|
144 | 134 | } |
| @@ -148,17 +138,8 export class Container { | |||
|
|
148 | 138 | return registration; |
|
|
149 | 139 | |
|
|
150 | 140 | if (isDependencyRegistration(registration)) { |
|
|
151 | ||
|
|
152 | return new ReferenceDescriptor({ | |
|
|
153 | name: registration.$dependency, | |
|
|
154 | lazy: registration.lazy, | |
|
|
155 | optional: registration.optional, | |
|
|
156 | default: registration.default, | |
|
|
157 | services: registration.services && this._parseObject(registration.services, resolver) | |
|
|
158 | }); | |
|
|
159 | ||
|
|
141 | return this._paseReference(registration, resolver); | |
|
|
160 | 142 | } else if (isValueRegistration(registration)) { |
|
|
161 | ||
|
|
162 | 143 | return !registration.parse ? |
|
|
163 | 144 | new ValueDescriptor(registration.$value) : |
|
|
164 | 145 | new AggregateDescriptor(this._parse(registration.$value, resolver)); |
| @@ -172,6 +153,16 export class Container { | |||
|
|
172 | 153 | return this._parseObject(registration, resolver); |
|
|
173 | 154 | } |
|
|
174 | 155 | |
|
|
156 | async _paseReference(registration: DependencyRegistration, resolver: ModuleResolverBase) { | |
|
|
157 | return new ReferenceDescriptor({ | |
|
|
158 | name: registration.$dependency, | |
|
|
159 | lazy: registration.lazy, | |
|
|
160 | optional: registration.optional, | |
|
|
161 | default: registration.default, | |
|
|
162 | services: registration.services && await this._parseRegistrations(registration.services, resolver) | |
|
|
163 | }); | |
|
|
164 | } | |
|
|
165 | ||
|
|
175 | 166 | async _parseService(data: ServiceRegistration, resolver: ModuleResolverBase) { |
|
|
176 | 167 | const opts: ServiceDescriptorParams = { |
|
|
177 | 168 | owner: this |
| @@ -194,12 +185,14 export class Container { | |||
|
|
194 | 185 | } |
|
|
195 | 186 | |
|
|
196 | 187 | if (data.services) |
|
|
197 |
opts.services = await this._parse |
|
|
|
188 | opts.services = await this._parseRegistrations(data.services, resolver); | |
|
|
198 | 189 | |
|
|
199 |
if (data.inject |
|
|
|
200 | opts.inject = await Promise.all(data.inject.map(x => this._parseObject(x, resolver))); | |
|
|
201 | else | |
|
|
202 | opts.inject = [await this._parseObject(data.inject, resolver)]; | |
|
|
190 | if (data.inject) { | |
|
|
191 | if (data.inject instanceof Array) | |
|
|
192 | opts.inject = await Promise.all(data.inject.map(x => this._parseObject(x, resolver))); | |
|
|
193 | else | |
|
|
194 | opts.inject = [await this._parseObject(data.inject, resolver)]; | |
|
|
195 | } | |
|
|
203 | 196 | |
|
|
204 | 197 | if (data.params) |
|
|
205 | 198 | opts.params = this._parse(data.params, resolver); |
| @@ -237,7 +230,7 export class Container { | |||
|
|
237 | 230 | return new ServiceDescriptor(opts); |
|
|
238 | 231 | } |
|
|
239 | 232 | |
|
|
240 | _parseObject(data: object, resolver: ModuleResolverBase) { | |
|
|
233 | async _parseObject(data: object, resolver: ModuleResolverBase) { | |
|
|
241 | 234 | if (data.constructor && |
|
|
242 | 235 | data.constructor.prototype !== Object.prototype) |
|
|
243 | 236 | return new ValueDescriptor(data); |
| @@ -245,16 +238,42 export class Container { | |||
|
|
245 | 238 | const o = {}; |
|
|
246 | 239 | |
|
|
247 | 240 | for (const p in data) |
|
|
248 | o[p] = this._parse(data[p], resolver); | |
|
|
241 | o[p] = await this._parse(data[p], resolver); | |
|
|
242 | ||
|
|
243 | // TODO: handle inline descriptors properly | |
|
|
244 | // const ex = { | |
|
|
245 | // activate(ctx) { | |
|
|
246 | // const value = ctx.activate(this.prop, "prop"); | |
|
|
247 | // // some code | |
|
|
248 | // }, | |
|
|
249 | ||
|
|
250 | // // will be turned to ReferenceDescriptor | |
|
|
251 | // prop: { $dependency: "depName" } | |
|
|
252 | // }; | |
|
|
249 | 253 | |
|
|
250 | 254 | return o; |
|
|
251 | 255 | } |
|
|
252 | 256 | |
|
|
253 | _parseArray(data: Array<any>, resolver: ModuleResolverBase) { | |
|
|
257 | async _parseArray(data: Array<any>, resolver: ModuleResolverBase) { | |
|
|
254 | 258 | if (data.constructor && |
|
|
255 | 259 | data.constructor.prototype !== Array.prototype) |
|
|
256 | 260 | return new ValueDescriptor(data); |
|
|
257 | 261 | |
|
|
258 |
return data |
|
|
|
262 | return pmap(data, x => this._parse(x, resolver)); | |
|
|
263 | } | |
|
|
264 | ||
|
|
265 | async _parseRegistrations(data: object, resolver: ModuleResolverBase) { | |
|
|
266 | if (data.constructor && | |
|
|
267 | data.constructor.prototype !== Object.prototype) | |
|
|
268 | throw new Error("Registrations must be a simple object"); | |
|
|
269 | ||
|
|
270 | const o: ServiceMap = {}; | |
|
|
271 | ||
|
|
272 | for (const p of Object.keys(data)) { | |
|
|
273 | const v = await this._parse(data[p], resolver); | |
|
|
274 | o[p] = isDescriptor(v) ? v : new AggregateDescriptor(v); | |
|
|
275 | } | |
|
|
276 | ||
|
|
277 | return o; | |
|
|
259 | 278 | } |
|
|
260 | 279 | } |
| @@ -27,41 +27,37 export class ReferenceDescriptor impleme | |||
|
|
27 | 27 | this._name = opts.name; |
|
|
28 | 28 | this._lazy = !!opts.lazy; |
|
|
29 | 29 | this._optional = !!opts.optional; |
|
|
30 |
this._default = |
|
|
|
30 | this._default = opts.default; | |
|
|
31 | 31 | this._services = opts.services; |
|
|
32 | 32 | } |
|
|
33 | 33 | |
|
|
34 | 34 | activate(context: ActivationContext, name: string) { |
|
|
35 | // добавляем сервисы | |
|
|
36 | if (this._services) { | |
|
|
37 | for (const p of Object.keys(this._services)) | |
|
|
38 | context.register(p, this._services[p]); | |
|
|
39 | } | |
|
|
35 | 40 | |
|
|
36 | 41 | if (this._lazy) { |
|
|
37 | // сохраняем контекст активации | |
|
|
38 | context = context.clone(); | |
|
|
39 | ||
|
|
40 | // добавляем сервисы | |
|
|
41 | if (this._services) { | |
|
|
42 | for (const p of Object.keys(this._services)) | |
|
|
43 | context.register(p, this._services[p]); | |
|
|
44 | } | |
|
|
42 | const saved = context.clone(); | |
|
|
45 | 43 | |
|
|
46 | 44 | return (cfg: ServiceMap) => { |
|
|
47 | 45 | // защищаем контекст на случай исключения в процессе |
|
|
48 | 46 | // активации |
|
|
49 |
const ct = |
|
|
|
47 | const ct = saved.clone(); | |
|
|
50 | 48 | try { |
|
|
51 | 49 | if (cfg) { |
|
|
52 | 50 | for (const k in cfg) |
|
|
53 | 51 | ct.register(k, cfg[k]); |
|
|
54 | 52 | } |
|
|
55 | 53 | |
|
|
56 |
return this._optional ? ct. |
|
|
|
57 |
. |
|
|
|
54 | return this._optional ? ct.resolve(this._name, this._default) : ct | |
|
|
55 | .resolve(this._name); | |
|
|
58 | 56 | } catch (error) { |
|
|
59 | 57 | throw new ActivationError(this._name, ct.getStack(), error); |
|
|
60 | 58 | } |
|
|
61 | 59 | }; |
|
|
62 | 60 | } else { |
|
|
63 | context.enter(name, this, !!this._services); | |
|
|
64 | ||
|
|
65 | 61 | // добавляем сервисы |
|
|
66 | 62 | if (this._services) { |
|
|
67 | 63 | for (const p of Object.keys(this._services)) |
| @@ -69,23 +65,13 export class ReferenceDescriptor impleme | |||
|
|
69 | 65 | } |
|
|
70 | 66 | |
|
|
71 | 67 | const v = this._optional ? |
|
|
72 |
context. |
|
|
|
73 |
context. |
|
|
|
74 | ||
|
|
75 | context.leave(); | |
|
|
68 | context.resolve(this._name, this._default) : | |
|
|
69 | context.resolve(this._name); | |
|
|
76 | 70 | |
|
|
77 | 71 | return v; |
|
|
78 | 72 | } |
|
|
79 | 73 | } |
|
|
80 | 74 | |
|
|
81 | isInstanceCreated() { | |
|
|
82 | return false; | |
|
|
83 | } | |
|
|
84 | ||
|
|
85 | getInstance() { | |
|
|
86 | throw new Error("The reference descriptor doesn't allowed to hold an instance"); | |
|
|
87 | } | |
|
|
88 | ||
|
|
89 | 75 | toString() { |
|
|
90 | 76 | const opts = []; |
|
|
91 | 77 | if (this._optional) |
| @@ -1,11 +1,14 | |||
|
|
1 | 1 | import { ActivationContext } from "./ActivationContext"; |
|
|
2 | import { Descriptor, ActivationType, ServiceMap } from "./interfaces"; | |
|
|
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 | import { TraceSource } from "../log/TraceSource"; | |
|
|
6 | 7 | |
|
|
7 | 8 | let cacheId = 0; |
|
|
8 | 9 | |
|
|
10 | const trace = TraceSource.get("@implab/core/di/ActivationContext"); | |
|
|
11 | ||
|
|
9 | 12 | function injectMethod(target, method, context, args) { |
|
|
10 | 13 | const m = target[method]; |
|
|
11 | 14 | if (!m) |
| @@ -29,6 +32,24 function makeClenupCallback(target, meth | |||
|
|
29 | 32 | } |
|
|
30 | 33 | } |
|
|
31 | 34 | |
|
|
35 | // TODO: make async | |
|
|
36 | function _parse(value, context: ActivationContext, path: string) { | |
|
|
37 | if (isPrimitive(value)) | |
|
|
38 | return value; | |
|
|
39 | ||
|
|
40 | if (isDescriptor(value)) | |
|
|
41 | return context.activate(value, path); | |
|
|
42 | ||
|
|
43 | if (value instanceof Array) | |
|
|
44 | return value.map((x, i) => this._parse(x, context, `${path}[${i}]`)); | |
|
|
45 | ||
|
|
46 | const t = {}; | |
|
|
47 | for (const p of Object.keys(value)) | |
|
|
48 | t[p] = this._parse(value[p], context, `${path}.${p}`); | |
|
|
49 | return t; | |
|
|
50 | ||
|
|
51 | } | |
|
|
52 | ||
|
|
32 | 53 | export interface ServiceDescriptorParams { |
|
|
33 | 54 | activation?: ActivationType; |
|
|
34 | 55 | |
| @@ -119,7 +140,7 export class ServiceDescriptor implement | |||
|
|
119 | 140 | } |
|
|
120 | 141 | } |
|
|
121 | 142 | |
|
|
122 |
activate(context: ActivationContext |
|
|
|
143 | activate(context: ActivationContext) { | |
|
|
123 | 144 | // if we have a local service records, register them first |
|
|
124 | 145 | let instance; |
|
|
125 | 146 | |
| @@ -135,7 +156,7 export class ServiceDescriptor implement | |||
|
|
135 | 156 | if (container.has(this._cacheId)) { |
|
|
136 | 157 | instance = container.get(this._cacheId); |
|
|
137 | 158 | } else { |
|
|
138 |
instance = this._create(context |
|
|
|
159 | instance = this._create(context); | |
|
|
139 | 160 | container.store(this._cacheId, instance); |
|
|
140 | 161 | if (this._cleanup) |
|
|
141 | 162 | container.onDispose( |
| @@ -152,7 +173,7 export class ServiceDescriptor implement | |||
|
|
152 | 173 | return this._instance; |
|
|
153 | 174 | |
|
|
154 | 175 | // create an instance |
|
|
155 |
instance = this._create(context |
|
|
|
176 | instance = this._create(context); | |
|
|
156 | 177 | |
|
|
157 | 178 | // the instance is bound to the container |
|
|
158 | 179 | if (this._cleanup) |
| @@ -168,12 +189,10 export class ServiceDescriptor implement | |||
|
|
168 | 189 | if (context.has(this._cacheId)) |
|
|
169 | 190 | return context.get(this._cacheId); |
|
|
170 | 191 | // context context activated instances are controlled by callers |
|
|
171 | return context.store(this._cacheId, this._create( | |
|
|
172 | context, | |
|
|
173 | name)); | |
|
|
192 | return context.store(this._cacheId, this._create(context)); | |
|
|
174 | 193 | case ActivationType.Call: // CALL |
|
|
175 | 194 | // per-call created instances are controlled by callers |
|
|
176 |
return this._create(context |
|
|
|
195 | return this._create(context); | |
|
|
177 | 196 | case ActivationType.Hierarchy: // HIERARCHY |
|
|
178 | 197 | // hierarchy activated instances are behave much like container activated |
|
|
179 | 198 | // except they are created and bound to the child container |
| @@ -182,7 +201,7 export class ServiceDescriptor implement | |||
|
|
182 | 201 | if (context.container.has(this._cacheId)) |
|
|
183 | 202 | return context.container.get(this._cacheId); |
|
|
184 | 203 | |
|
|
185 |
instance = this._create(context |
|
|
|
204 | instance = this._create(context); | |
|
|
186 | 205 | |
|
|
187 | 206 | if (this._cleanup) |
|
|
188 | 207 | context.container.onDispose(makeClenupCallback( |
| @@ -203,8 +222,8 export class ServiceDescriptor implement | |||
|
|
203 | 222 | return this._instance; |
|
|
204 | 223 | } |
|
|
205 | 224 | |
|
|
206 |
_create(context |
|
|
|
207 | context.enter(name, this, Boolean(this._services)); | |
|
|
225 | _create(context: ActivationContext) { | |
|
|
226 | trace.debug(`constructing ${context._name}`); | |
|
|
208 | 227 | |
|
|
209 | 228 | if (this._activationType !== ActivationType.Call && |
|
|
210 | 229 | context.visit(this._cacheId) > 0) |
| @@ -235,13 +254,9 export class ServiceDescriptor implement | |||
|
|
235 | 254 | if (this._params === undefined) { |
|
|
236 | 255 | instance = this._factory(); |
|
|
237 | 256 | } else if (this._params instanceof Array) { |
|
|
238 |
instance = this._factory.apply(this, context |
|
|
|
239 | this._params, | |
|
|
240 | ".params")); | |
|
|
257 | instance = this._factory.apply(this, _parse(this._params, context, "args")); | |
|
|
241 | 258 | } else { |
|
|
242 |
instance = this._factory(context |
|
|
|
243 | this._params, | |
|
|
244 | ".params")); | |
|
|
259 | instance = this._factory(_parse(this._params, context, "args")); | |
|
|
245 | 260 | } |
|
|
246 | 261 | |
|
|
247 | 262 | if (this._inject) { |
| @@ -251,8 +266,6 export class ServiceDescriptor implement | |||
|
|
251 | 266 | }); |
|
|
252 | 267 | } |
|
|
253 | 268 | |
|
|
254 | context.leave(); | |
|
|
255 | ||
|
|
256 | 269 | return instance; |
|
|
257 | 270 | } |
|
|
258 | 271 | |
| @@ -1,5 +1,4 | |||
|
|
1 | 1 | import { Descriptor } from "./interfaces"; |
|
|
2 | import { ActivationContext } from "./ActivationContext"; | |
|
|
3 | 2 | |
|
|
4 | 3 | export class ValueDescriptor implements Descriptor { |
|
|
5 | 4 | _value; |
| @@ -8,16 +7,11 export class ValueDescriptor implements | |||
|
|
8 | 7 | this._value = value; |
|
|
9 | 8 | } |
|
|
10 | 9 | |
|
|
11 | activate(context: ActivationContext, name: string) { | |
|
|
12 | context.enter(name); | |
|
|
13 | const v = this._value; | |
|
|
14 | context.leave(); | |
|
|
15 | return v; | |
|
|
16 | } | |
|
|
17 | isInstanceCreated(): boolean { | |
|
|
18 | return true; | |
|
|
19 | } | |
|
|
20 | getInstance() { | |
|
|
10 | activate() { | |
|
|
21 | 11 | return this._value; |
|
|
22 | 12 | } |
|
|
13 | ||
|
|
14 | toString() { | |
|
|
15 | return `@type=${typeof this._value}`; | |
|
|
16 | } | |
|
|
23 | 17 | } |
| @@ -4,8 +4,6 import { Constructor, Factory } from ".. | |||
|
|
4 | 4 | |
|
|
5 | 5 | export interface Descriptor { |
|
|
6 | 6 | activate(context: ActivationContext, name?: string); |
|
|
7 | isInstanceCreated(): boolean; | |
|
|
8 | getInstance(); | |
|
|
9 | 7 | } |
|
|
10 | 8 | |
|
|
11 | 9 | export function isDescriptor(x): x is Descriptor { |
| @@ -14,7 +12,7 export function isDescriptor(x): x is De | |||
|
|
14 | 12 | } |
|
|
15 | 13 | |
|
|
16 | 14 | export interface ServiceMap { |
|
|
17 |
[s: string]: |
|
|
|
15 | [s: string]: Descriptor; | |
|
|
18 | 16 | } |
|
|
19 | 17 | |
|
|
20 | 18 | export enum ActivationType { |
| @@ -1,5 +1,5 | |||
|
|
1 | 1 | let _nextOid = 0; |
|
|
2 | const _oid = Symbol("__oid"); | |
|
|
2 | const _oid = typeof Symbol === "function" ? Symbol("__oid") : "__oid"; | |
|
|
3 | 3 | |
|
|
4 | 4 | export function oid(instance: object): string { |
|
|
5 | 5 | if (isNull(instance)) |
| @@ -232,7 +232,7 export function delegate<T, K extends ke | |||
|
|
232 | 232 | export function pmap(items, cb) { |
|
|
233 | 233 | argumentNotNull(cb, "cb"); |
|
|
234 | 234 | |
|
|
235 | if (items && items.then instanceof Function) | |
|
|
235 | if (isPromise(items)) | |
|
|
236 | 236 | return items.then(data => pmap(data, cb)); |
|
|
237 | 237 | |
|
|
238 | 238 | if (isNull(items) || !items.length) |
| @@ -1,14 +1,26 | |||
|
|
1 | import { test } from "./TestTraits"; | |
|
|
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 | import { ValueDescriptor } from "@implab/core/di/ValueDescriptor"; | |
|
|
6 | import { TraceSource, DebugLevel } from "@implab/core/log/TraceSource"; | |
|
|
7 | import { Foo } from "./mock/Foo"; | |
|
|
8 | import { Bar } from "./mock/Bar"; | |
|
|
9 | import { isNull } from "@implab/core/safe"; | |
|
|
5 | 10 | |
|
|
6 |
test("Container register/ |
|
|
|
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 | ||
|
|
7 | 19 | const container = new Container(); |
|
|
8 | 20 | |
|
|
9 | 21 | const connection1 = "db://localhost"; |
|
|
10 | 22 | |
|
|
11 | container.register("connection", connection1); | |
|
|
23 | container.register("connection", new ValueDescriptor(connection1)); | |
|
|
12 | 24 | |
|
|
13 | 25 | t.equals(container.getService("connection"), connection1); |
|
|
14 | 26 | |
| @@ -16,11 +28,47 test("Container register/getService test | |||
|
|
16 | 28 | "dbParams", |
|
|
17 | 29 | new AggregateDescriptor({ |
|
|
18 | 30 | timeout: 10, |
|
|
19 | connection: new ReferenceDescriptor({name: "connection"}) | |
|
|
31 | connection: new ReferenceDescriptor({ name: "connection" }) | |
|
|
20 | 32 | }) |
|
|
21 | 33 | ); |
|
|
22 | 34 | |
|
|
23 | 35 | const dbParams = container.getService("dbParams"); |
|
|
24 | t.equals(dbParams.connection, connection1); | |
|
|
36 | t.equals(dbParams.connection, connection1, "should get connection"); | |
|
|
37 | ||
|
|
38 | writer.destroy(); | |
|
|
39 | }); | |
|
|
40 | ||
|
|
41 | test("Container configure/resolve tests", async t => { | |
|
|
42 | const writer = new TapeWriter(t); | |
|
|
43 | ||
|
|
44 | TraceSource.on(ts => { | |
|
|
45 | ts.level = DebugLevel; | |
|
|
46 | writer.writeEvents(ts.events); | |
|
|
47 | }); | |
|
|
48 | ||
|
|
49 | const container = new Container(); | |
|
|
50 | ||
|
|
51 | await container.configure({ | |
|
|
52 | foo: { | |
|
|
53 | $type: Foo | |
|
|
54 | }, | |
|
|
25 | 55 | |
|
|
56 | bar: { | |
|
|
57 | $type: Bar, | |
|
|
58 | params: { | |
|
|
59 | db: { | |
|
|
60 | provider: { | |
|
|
61 | $dependency: "db" | |
|
|
62 | } | |
|
|
63 | } | |
|
|
64 | } | |
|
|
65 | } | |
|
|
66 | }); | |
|
|
67 | ||
|
|
68 | const f1 = container.resolve("foo"); | |
|
|
69 | t.assert(!isNull(f1), "foo should be not null"); | |
|
|
70 | ||
|
|
71 | const b1 = container.resolve("bar"); | |
|
|
72 | ||
|
|
73 | writer.destroy(); | |
|
|
26 | 74 | }); |
General Comments 0
You need to be logged in to leave comments.
Login now
