| @@ -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 | import { TraceSource } from "../log/TraceSource"; |
|
1 | import { TraceSource } from "../log/TraceSource"; | |
| 2 | import { argumentNotNull, argumentNotEmptyString, isPrimitive, each, isNull } from "../safe"; |
|
2 | import { argumentNotNull, argumentNotEmptyString, isPrimitive, each, isNull } from "../safe"; | |
| 3 |
import { Descriptor, ServiceMap |
|
3 | import { Descriptor, ServiceMap } from "./interfaces"; | |
| 4 | import { Container } from "./Container"; |
|
4 | import { Container } from "./Container"; | |
| 5 |
|
5 | |||
| 6 | const trace = TraceSource.get("@implab/core/di/ActivationContext"); |
|
6 | const trace = TraceSource.get("@implab/core/di/ActivationContext"); | |
| @@ -1,14 +1,17 | |||||
| 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 } from "./interfaces"; |
|
4 | import { isDescriptor, ActivationType, ServiceMap, isDependencyRegistration, isValueRegistration, ServiceRegistration, DependencyRegistration, ValueRegistration } from "./interfaces"; | |
| 5 | import { AggregateDescriptor } from "./AggregateDescriptor"; |
|
5 | import { AggregateDescriptor } from "./AggregateDescriptor"; | |
| 6 | import { isPrimitive, pmap } from "../safe"; |
|
6 | import { isPrimitive, pmap } from "../safe"; | |
| 7 | import { ReferenceDescriptor } from "./ReferenceDescriptor"; |
|
7 | import { ReferenceDescriptor } from "./ReferenceDescriptor"; | |
| 8 | import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor"; |
|
8 | import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor"; | |
| 9 | import { ModuleResolverBase } from "./ModuleResolverBase"; |
|
9 | import { ModuleResolverBase } from "./ModuleResolverBase"; | |
| 10 | import format = require("../text/format"); |
|
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 | export class Container { |
|
16 | export class Container { | |
| 14 | _services: ServiceMap; |
|
17 | _services: ServiceMap; | |
| @@ -30,6 +33,7 export class Container { | |||||
| 30 | this._cleanup = []; |
|
33 | this._cleanup = []; | |
| 31 | this._root = parent ? parent.getRootContainer() : this; |
|
34 | this._root = parent ? parent.getRootContainer() : this; | |
| 32 | this._services.container = new ValueDescriptor(this); |
|
35 | this._services.container = new ValueDescriptor(this); | |
|
|
36 | this._resolver = new RequireJsResolver(); | |||
| 33 | } |
|
37 | } | |
| 34 |
|
38 | |||
| 35 | getRootContainer() { |
|
39 | getRootContainer() { | |
| @@ -57,6 +61,9 export class Container { | |||||
| 57 | } |
|
61 | } | |
| 58 | } |
|
62 | } | |
| 59 |
|
63 | |||
|
|
64 | /** | |||
|
|
65 | * @deprecated use resolve() method | |||
|
|
66 | */ | |||
| 60 | getService(name: string, def?) { |
|
67 | getService(name: string, def?) { | |
| 61 | return this.resolve.apply(this, arguments); |
|
68 | return this.resolve.apply(this, arguments); | |
| 62 | } |
|
69 | } | |
| @@ -101,10 +108,12 export class Container { | |||||
| 101 | */ |
|
108 | */ | |
| 102 | async configure(config: string | object, opts?: object) { |
|
109 | async configure(config: string | object, opts?: object) { | |
| 103 | if (typeof (config) === "string") { |
|
110 | if (typeof (config) === "string") { | |
|
|
111 | trace.log(`load configuration '${config}'`); | |||
| 104 | const resolver = await this._resolver.createResolver(config, opts); |
|
112 | const resolver = await this._resolver.createResolver(config, opts); | |
| 105 | const data = await this._resolver.loadModule(config); |
|
113 | const data = await this._resolver.loadModule(config); | |
| 106 | return this._configure(data, { resolver }); |
|
114 | return this._configure(data, { resolver }); | |
| 107 | } else { |
|
115 | } else { | |
|
|
116 | trace.log(`json configuration`); | |||
| 108 | return this._configure(config); |
|
117 | return this._configure(config); | |
| 109 | } |
|
118 | } | |
| 110 | } |
|
119 | } | |
| @@ -133,27 +142,30 export class Container { | |||||
| 133 | this.register(services); |
|
142 | this.register(services); | |
| 134 | } |
|
143 | } | |
| 135 |
|
144 | |||
| 136 |
async _parse( |
|
145 | async _parse(data: any, resolver: ModuleResolverBase) { | |
| 137 |
if (isPrimitive( |
|
146 | if (isPrimitive(data) || isDescriptor(data)) | |
| 138 |
return |
|
147 | return data; | |
| 139 |
|
148 | |||
| 140 |
if (isDependencyRegistration( |
|
149 | if (isDependencyRegistration(data)) { | |
| 141 |
return this._ |
|
150 | return this._makeReferenceDescriptor(data, resolver); | |
| 142 |
} else if (isValueRegistration( |
|
151 | } else if (isValueRegistration(data)) { | |
| 143 | return !registration.parse ? |
|
152 | return this._makeValueDescriptor(data, resolver); | |
| 144 | new ValueDescriptor(registration.$value) : |
|
153 | } else if (data.$type || data.$factory) { | |
| 145 | new AggregateDescriptor(this._parse(registration.$value, resolver)); |
|
154 | return this._makeServiceDescriptor(data, resolver); | |
| 146 |
|
155 | } else if (data instanceof Array) { | ||
| 147 | } else if (registration.$type || registration.$factory) { |
|
156 | return this._parseArray(data, resolver); | |
| 148 | return this._parseService(registration, resolver); |
|
|||
| 149 | } else if (registration instanceof Array) { |
|
|||
| 150 | return this._parseArray(registration, 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 | return new ReferenceDescriptor({ |
|
169 | return new ReferenceDescriptor({ | |
| 158 | name: registration.$dependency, |
|
170 | name: registration.$dependency, | |
| 159 | lazy: registration.lazy, |
|
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 | const opts: ServiceDescriptorParams = { |
|
179 | const opts: ServiceDescriptorParams = { | |
| 168 | owner: this |
|
180 | owner: this | |
| 169 | }; |
|
181 | }; | |
| @@ -195,7 +207,7 export class Container { | |||||
| 195 | } |
|
207 | } | |
| 196 |
|
208 | |||
| 197 | if (data.params) |
|
209 | if (data.params) | |
| 198 | opts.params = this._parse(data.params, resolver); |
|
210 | opts.params = await this._parse(data.params, resolver); | |
| 199 |
|
211 | |||
| 200 | if (data.activation) { |
|
212 | if (data.activation) { | |
| 201 | if (typeof (data.activation) === "string") { |
|
213 | if (typeof (data.activation) === "string") { | |
| @@ -3,9 +3,12 import { Uuid } from "../Uuid"; | |||||
| 3 | import { argumentNotEmptyString } from "../safe"; |
|
3 | import { argumentNotEmptyString } from "../safe"; | |
| 4 | import { TraceSource } from "../log/TraceSource"; |
|
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 | interface RequireJsResolverParams { |
|
13 | interface RequireJsResolverParams { | |
| 11 | contextRequire: (modules: string[], cb?: (...args: any[]) => any) => void; |
|
14 | contextRequire: (modules: string[], cb?: (...args: any[]) => any) => void; | |
| @@ -13,14 +16,14 interface RequireJsResolverParams { | |||||
| 13 | base: string; |
|
16 | base: string; | |
| 14 | } |
|
17 | } | |
| 15 |
|
18 | |||
| 16 | TraceSource.get("RequireJsResolver"); |
|
19 | const trace = TraceSource.get("@implab/core/di/RequireJsResolver"); | |
| 17 |
|
20 | |||
| 18 | export class RequireJsResolver extends ModuleResolverBase { |
|
21 | export class RequireJsResolver extends ModuleResolverBase { | |
| 19 | _contextRequire = require; |
|
22 | _contextRequire = require; | |
| 20 |
|
23 | |||
| 21 | _base: string; |
|
24 | _base: string; | |
| 22 |
|
25 | |||
| 23 | constructor(opts) { |
|
26 | constructor(opts?: RequireJsResolverParams) { | |
| 24 | super(); |
|
27 | super(); | |
| 25 |
|
28 | |||
| 26 | if (opts) { |
|
29 | if (opts) { | |
| @@ -40,6 +43,8 export class RequireJsResolver extends M | |||||
| 40 | async createResolver(moduleName: string): Promise<ModuleResolverBase> { |
|
43 | async createResolver(moduleName: string): Promise<ModuleResolverBase> { | |
| 41 | argumentNotEmptyString(moduleName, "moduleName"); |
|
44 | argumentNotEmptyString(moduleName, "moduleName"); | |
| 42 |
|
45 | |||
|
|
46 | trace.log("createResolver({0})", moduleName); | |||
|
|
47 | ||||
| 43 | const parts = moduleName.split("/"); |
|
48 | const parts = moduleName.split("/"); | |
| 44 | if (parts[0] === ".") { |
|
49 | if (parts[0] === ".") { | |
| 45 | if (this._base) |
|
50 | if (this._base) | |
| @@ -55,17 +60,38 export class RequireJsResolver extends M | |||||
| 55 |
|
60 | |||
| 56 | const shim = parts.join("/"); |
|
61 | const shim = parts.join("/"); | |
| 57 |
|
62 | |||
| 58 | const contextRequire = await new Promise( |
|
63 | trace.debug(`define shim ${shim}`); | |
| 59 | resolve => define(shim, ["require"], resolve) |
|
|||
| 60 | ); |
|
|||
| 61 |
|
64 | |||
| 62 | return new RequireJsResolver({ |
|
65 | try { | |
| 63 | base: parts.slice(0, -1).join("/"), |
|
66 | const contextRequire = await new Promise<RequireFn>( | |
| 64 | contextRequire |
|
67 | (resolve, reject) => { | |
| 65 | }); |
|
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 | async loadModule(moduleName: string): Promise<object> { |
|
93 | async loadModule(moduleName: string): Promise<object> { | |
|
|
94 | trace.log(`loadModule(${moduleName})`); | |||
| 69 | return new Promise<object>( |
|
95 | return new Promise<object>( | |
| 70 | resolve => this._contextRequire.call(null, [moduleName], resolve) |
|
96 | resolve => this._contextRequire.call(null, [moduleName], resolve) | |
| 71 | ); |
|
97 | ); | |
| @@ -37,17 +37,19 function _parse(value, context: Activati | |||||
| 37 | if (isPrimitive(value)) |
|
37 | if (isPrimitive(value)) | |
| 38 | return value; |
|
38 | return value; | |
| 39 |
|
39 | |||
|
|
40 | trace.debug("parse {0}", path); | |||
|
|
41 | ||||
| 40 | if (isDescriptor(value)) |
|
42 | if (isDescriptor(value)) | |
| 41 | return context.activate(value, path); |
|
43 | return context.activate(value, path); | |
| 42 |
|
44 | |||
| 43 | if (value instanceof Array) |
|
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 | const t = {}; |
|
48 | const t = {}; | |
| 47 | for (const p of Object.keys(value)) |
|
49 | for (const p of Object.keys(value)) | |
| 48 |
t[p] = |
|
50 | t[p] = _parse(value[p], context, `${path}.${p}`); | |
|
|
51 | ||||
| 49 | return t; |
|
52 | return t; | |
| 50 |
|
||||
| 51 | } |
|
53 | } | |
| 52 |
|
54 | |||
| 53 | export interface ServiceDescriptorParams { |
|
55 | export interface ServiceDescriptorParams { | |
| @@ -62,5 +62,5 export function isValueRegistration(x): | |||||
| 62 | } |
|
62 | } | |
| 63 |
|
63 | |||
| 64 | export function isDependencyRegistration(x): x is DependencyRegistration { |
|
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 |
|
1 | import * as format from "../text/format"; | |
| 2 |
import { argumentNotNull } from |
|
2 | import { argumentNotNull } from "../safe"; | |
| 3 |
import { Observable } from |
|
3 | import { Observable } from "../Observable"; | |
| 4 |
import { IDestroyable } from |
|
4 | import { IDestroyable } from "../interfaces"; | |
| 5 |
|
5 | |||
| 6 | export const DebugLevel = 400; |
|
6 | export const DebugLevel = 400; | |
| 7 |
|
7 | |||
| @@ -16,11 +16,11 export const SilentLevel = 0; | |||||
| 16 | export class TraceEvent { |
|
16 | export class TraceEvent { | |
| 17 | readonly source: TraceSource; |
|
17 | readonly source: TraceSource; | |
| 18 |
|
18 | |||
| 19 |
readonly level: |
|
19 | readonly level: number; | |
| 20 |
|
20 | |||
| 21 | readonly arg: any; |
|
21 | readonly arg: any; | |
| 22 |
|
22 | |||
| 23 |
constructor(source: TraceSource, level: |
|
23 | constructor(source: TraceSource, level: number, arg: any) { | |
| 24 | this.source = source; |
|
24 | this.source = source; | |
| 25 | this.level = level; |
|
25 | this.level = level; | |
| 26 | this.arg = arg; |
|
26 | this.arg = arg; | |
| @@ -185,4 +185,3 export class TraceSource { | |||||
| 185 | return Registry.instance.get(id); |
|
185 | return Registry.instance.get(id); | |
| 186 | } |
|
186 | } | |
| 187 | } |
|
187 | } | |
| 188 |
|
||||
| @@ -20,20 +20,31 test("Container register/resolve tests", | |||||
| 20 |
|
20 | |||
| 21 | const connection1 = "db://localhost"; |
|
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"); | |
| 28 | "dbParams", |
|
34 | ||
| 29 | new AggregateDescriptor({ |
|
35 | t.doesNotThrow( | |
| 30 | timeout: 10, |
|
36 | () => container.register( | |
| 31 | connection: new ReferenceDescriptor({ name: "connection" }) |
|
37 | "dbParams", | |
| 32 | }) |
|
38 | new AggregateDescriptor({ | |
|
|
39 | timeout: 10, | |||
|
|
40 | connection: new ReferenceDescriptor({ name: "connection" }) | |||
|
|
41 | }) | |||
|
|
42 | ), | |||
|
|
43 | "register AggregateDescriptor" | |||
| 33 | ); |
|
44 | ); | |
| 34 |
|
45 | |||
| 35 | const dbParams = container.getService("dbParams"); |
|
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 | writer.destroy(); |
|
49 | writer.destroy(); | |
| 39 | }); |
|
50 | }); | |
| @@ -53,6 +64,13 test("Container configure/resolve tests" | |||||
| 53 | $type: Foo |
|
64 | $type: Foo | |
| 54 | }, |
|
65 | }, | |
| 55 |
|
66 | |||
|
|
67 | box: { | |||
|
|
68 | $type: Bar, | |||
|
|
69 | params: { | |||
|
|
70 | $dependency: "foo" | |||
|
|
71 | } | |||
|
|
72 | }, | |||
|
|
73 | ||||
| 56 | bar: { |
|
74 | bar: { | |
| 57 | $type: Bar, |
|
75 | $type: Bar, | |
| 58 | params: { |
|
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 | const f1 = container.resolve("foo"); |
|
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 | t.assert(!isNull(f1), "foo should be not null"); |
|
111 | t.assert(!isNull(f1), "foo should be not null"); | |
| 70 |
|
112 | |||
| 71 | const b1 = container.resolve("bar"); |
|
113 | const b1 = container.resolve("bar"); | |
| 72 |
|
114 | |||
|
|
115 | t.assert(!isNull(b1), "foo should be not null"); | |||
|
|
116 | ||||
| 73 | writer.destroy(); |
|
117 | writer.destroy(); | |
| 74 | }); |
|
118 | }); | |
General Comments 0
You need to be logged in to leave comments.
Login now
