| @@ -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,6 +1,6 | |||
|
|
1 | 1 | import { TraceSource } from "../log/TraceSource"; |
|
|
2 | 2 | import { argumentNotNull, argumentNotEmptyString, isPrimitive, each, isNull } from "../safe"; |
|
|
3 |
import { Descriptor, ServiceMap |
|
|
|
3 | import { Descriptor, ServiceMap } from "./interfaces"; | |
|
|
4 | 4 | import { Container } from "./Container"; |
|
|
5 | 5 | |
|
|
6 | 6 | const trace = TraceSource.get("@implab/core/di/ActivationContext"); |
| @@ -1,14 +1,17 | |||
|
|
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 { |
|
|
|
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; |
| @@ -30,6 +33,7 export class Container { | |||
|
|
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() { |
| @@ -57,6 +61,9 export class Container { | |||
|
|
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 | } |
| @@ -101,10 +108,12 export class Container { | |||
|
|
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 | } |
| @@ -133,27 +142,30 export class Container { | |||
|
|
133 | 142 | this.register(services); |
|
|
134 | 143 | } |
|
|
135 | 144 | |
|
|
136 |
async _parse( |
|
|
|
137 |
if (isPrimitive( |
|
|
|
138 |
return |
|
|
|
145 | async _parse(data: any, resolver: ModuleResolverBase) { | |
|
|
146 | if (isPrimitive(data) || isDescriptor(data)) | |
|
|
147 | return data; | |
|
|
139 | 148 | |
|
|
140 |
if (isDependencyRegistration( |
|
|
|
141 |
return this._ |
|
|
|
142 |
} else if (isValueRegistration( |
|
|
|
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( |
|
|
|
159 | return this._parseObject(data, resolver); | |
|
|
154 | 160 | } |
|
|
155 | 161 | |
|
|
156 |
async _ |
|
|
|
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, |
| @@ -163,7 +175,7 export class Container { | |||
|
|
163 | 175 | }); |
|
|
164 | 176 | } |
|
|
165 | 177 | |
|
|
166 |
async _ |
|
|
|
178 | async _makeServiceDescriptor(data: ServiceRegistration, resolver: ModuleResolverBase) { | |
|
|
167 | 179 | const opts: ServiceDescriptorParams = { |
|
|
168 | 180 | owner: this |
|
|
169 | 181 | }; |
| @@ -195,7 +207,7 export class Container { | |||
|
|
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") { |
| @@ -3,9 +3,12 import { Uuid } from "../Uuid"; | |||
|
|
3 | 3 | import { argumentNotEmptyString } from "../safe"; |
|
|
4 | 4 | import { TraceSource } from "../log/TraceSource"; |
|
|
5 | 5 | |
|
|
6 |
|
|
|
|
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; |
| @@ -13,14 +16,14 interface RequireJsResolverParams { | |||
|
|
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) { |
| @@ -40,6 +43,8 export class RequireJsResolver extends M | |||
|
|
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) |
| @@ -55,17 +60,38 export class RequireJsResolver extends M | |||
|
|
55 | 60 | |
|
|
56 | 61 | const shim = parts.join("/"); |
|
|
57 | 62 | |
|
|
58 | const contextRequire = await new Promise( | |
|
|
59 | resolve => define(shim, ["require"], resolve) | |
|
|
63 | trace.debug(`define shim ${shim}`); | |
|
|
64 | ||
|
|
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 | } | |
|
|
60 | 78 | ); |
|
|
61 | 79 | |
|
|
80 | trace.debug("creating new moduleResolver"); | |
|
|
81 | ||
|
|
62 | 82 | return new RequireJsResolver({ |
|
|
63 | 83 | base: parts.slice(0, -1).join("/"), |
|
|
64 | 84 | contextRequire |
|
|
65 | 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 | ); |
| @@ -37,17 +37,19 function _parse(value, context: Activati | |||
|
|
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) => |
|
|
|
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] = |
|
|
|
50 | t[p] = _parse(value[p], context, `${path}.${p}`); | |
|
|
51 | ||
|
|
49 | 52 | return t; |
|
|
50 | ||
|
|
51 | 53 | } |
|
|
52 | 54 | |
|
|
53 | 55 | export interface ServiceDescriptorParams { |
| @@ -62,5 +62,5 export function isValueRegistration(x): | |||
|
|
62 | 62 | } |
|
|
63 | 63 | |
|
|
64 | 64 | export function isDependencyRegistration(x): x is DependencyRegistration { |
|
|
65 |
return (!isPrimitive(x)) && ("$dep |
|
|
|
65 | return (!isPrimitive(x)) && ("$dependency" in x); | |
|
|
66 | 66 | } |
| @@ -1,7 +1,7 | |||
|
|
1 |
import * as format from |
|
|
|
2 |
import { argumentNotNull } from |
|
|
|
3 |
import { Observable } from |
|
|
|
4 |
import { IDestroyable } from |
|
|
|
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 | |
| @@ -16,11 +16,11 export const SilentLevel = 0; | |||
|
|
16 | 16 | export class TraceEvent { |
|
|
17 | 17 | readonly source: TraceSource; |
|
|
18 | 18 | |
|
|
19 |
readonly level: |
|
|
|
19 | readonly level: number; | |
|
|
20 | 20 | |
|
|
21 | 21 | readonly arg: any; |
|
|
22 | 22 | |
|
|
23 |
constructor(source: TraceSource, level: |
|
|
|
23 | constructor(source: TraceSource, level: number, arg: any) { | |
|
|
24 | 24 | this.source = source; |
|
|
25 | 25 | this.level = level; |
|
|
26 | 26 | this.arg = arg; |
| @@ -185,4 +185,3 export class TraceSource { | |||
|
|
185 | 185 | return Registry.instance.get(id); |
|
|
186 | 186 | } |
|
|
187 | 187 | } |
|
|
188 | ||
| @@ -20,20 +20,31 test("Container register/resolve tests", | |||
|
|
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( | |
|
|
33 | t.equals(container.getService("connection"), connection1, "resolve string value"); | |
|
|
34 | ||
|
|
35 | t.doesNotThrow( | |
|
|
36 | () => container.register( | |
|
|
28 | 37 | "dbParams", |
|
|
29 | 38 | new AggregateDescriptor({ |
|
|
30 | 39 | timeout: 10, |
|
|
31 | 40 | connection: new ReferenceDescriptor({ name: "connection" }) |
|
|
32 | 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 | }); |
| @@ -53,6 +64,13 test("Container configure/resolve tests" | |||
|
|
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: { |
| @@ -64,11 +82,37 test("Container configure/resolve tests" | |||
|
|
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
