diff --git a/src/ts/di/ActivationContext.ts b/src/ts/di/ActivationContext.ts --- a/src/ts/di/ActivationContext.ts +++ b/src/ts/di/ActivationContext.ts @@ -1,6 +1,6 @@ import { TraceSource } from "../log/TraceSource"; import { argumentNotNull, argumentNotEmptyString, isPrimitive, each, isNull } from "../safe"; -import { Descriptor, ServiceMap, isDescriptor } from "./interfaces"; +import { Descriptor, ServiceMap } from "./interfaces"; import { Container } from "./Container"; const trace = TraceSource.get("@implab/core/di/ActivationContext"); diff --git a/src/ts/di/Container.ts b/src/ts/di/Container.ts --- a/src/ts/di/Container.ts +++ b/src/ts/di/Container.ts @@ -1,14 +1,17 @@ import { ActivationContext } from "./ActivationContext"; import { ValueDescriptor } from "./ValueDescriptor"; import { ActivationError } from "./ActivationError"; -import { isDescriptor, ActivationType, ServiceMap, isDependencyRegistration, isValueRegistration, ServiceRegistration, DependencyRegistration } from "./interfaces"; +import { isDescriptor, ActivationType, ServiceMap, isDependencyRegistration, isValueRegistration, ServiceRegistration, DependencyRegistration, ValueRegistration } from "./interfaces"; import { AggregateDescriptor } from "./AggregateDescriptor"; import { isPrimitive, pmap } from "../safe"; import { ReferenceDescriptor } from "./ReferenceDescriptor"; import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor"; import { ModuleResolverBase } from "./ModuleResolverBase"; import format = require("../text/format"); -import { throws } from "assert"; +import { TraceSource } from "../log/TraceSource"; +import { RequireJsResolver } from "./RequireJsResolver"; + +const trace = TraceSource.get("@implab/core/di/ActivationContext"); export class Container { _services: ServiceMap; @@ -30,6 +33,7 @@ export class Container { this._cleanup = []; this._root = parent ? parent.getRootContainer() : this; this._services.container = new ValueDescriptor(this); + this._resolver = new RequireJsResolver(); } getRootContainer() { @@ -57,6 +61,9 @@ export class Container { } } + /** + * @deprecated use resolve() method + */ getService(name: string, def?) { return this.resolve.apply(this, arguments); } @@ -101,10 +108,12 @@ export class Container { */ async configure(config: string | object, opts?: object) { if (typeof (config) === "string") { + trace.log(`load configuration '${config}'`); const resolver = await this._resolver.createResolver(config, opts); const data = await this._resolver.loadModule(config); return this._configure(data, { resolver }); } else { + trace.log(`json configuration`); return this._configure(config); } } @@ -133,27 +142,30 @@ export class Container { this.register(services); } - async _parse(registration: any, resolver: ModuleResolverBase) { - if (isPrimitive(registration) || isDescriptor(registration)) - return registration; + async _parse(data: any, resolver: ModuleResolverBase) { + if (isPrimitive(data) || isDescriptor(data)) + return data; - if (isDependencyRegistration(registration)) { - return this._paseReference(registration, resolver); - } else if (isValueRegistration(registration)) { - return !registration.parse ? - new ValueDescriptor(registration.$value) : - new AggregateDescriptor(this._parse(registration.$value, resolver)); - - } else if (registration.$type || registration.$factory) { - return this._parseService(registration, resolver); - } else if (registration instanceof Array) { - return this._parseArray(registration, resolver); + if (isDependencyRegistration(data)) { + return this._makeReferenceDescriptor(data, resolver); + } else if (isValueRegistration(data)) { + return this._makeValueDescriptor(data, resolver); + } else if (data.$type || data.$factory) { + return this._makeServiceDescriptor(data, resolver); + } else if (data instanceof Array) { + return this._parseArray(data, resolver); } - return this._parseObject(registration, resolver); + return this._parseObject(data, resolver); } - async _paseReference(registration: DependencyRegistration, resolver: ModuleResolverBase) { + async _makeValueDescriptor(data: ValueRegistration, resolver: ModuleResolverBase) { + return !data.parse ? + new ValueDescriptor(data.$value) : + new AggregateDescriptor(this._parse(data.$value, resolver)); + } + + async _makeReferenceDescriptor(registration: DependencyRegistration, resolver: ModuleResolverBase) { return new ReferenceDescriptor({ name: registration.$dependency, lazy: registration.lazy, @@ -163,7 +175,7 @@ export class Container { }); } - async _parseService(data: ServiceRegistration, resolver: ModuleResolverBase) { + async _makeServiceDescriptor(data: ServiceRegistration, resolver: ModuleResolverBase) { const opts: ServiceDescriptorParams = { owner: this }; @@ -195,7 +207,7 @@ export class Container { } if (data.params) - opts.params = this._parse(data.params, resolver); + opts.params = await this._parse(data.params, resolver); if (data.activation) { if (typeof (data.activation) === "string") { diff --git a/src/ts/di/RequireJsResolver.ts b/src/ts/di/RequireJsResolver.ts --- a/src/ts/di/RequireJsResolver.ts +++ b/src/ts/di/RequireJsResolver.ts @@ -3,9 +3,12 @@ import { Uuid } from "../Uuid"; import { argumentNotEmptyString } from "../safe"; import { TraceSource } from "../log/TraceSource"; -declare function require(modules: string[], cb?: (...args: any[]) => any): void; +type RequireFn = (modules: string[], cb?: (...args: any[]) => any) => void; -declare function define(name: string, modules: string[], cb?: (...args: any[]) => any): void; +declare const require: RequireFn; + +declare function define(name: string, modules: string[], cb?: (...args: any[]) => any, eb?: (e) => any): void; +declare function define(modules: string[], cb?: (...args: any[]) => any, eb?: (e) => any): void; interface RequireJsResolverParams { contextRequire: (modules: string[], cb?: (...args: any[]) => any) => void; @@ -13,14 +16,14 @@ interface RequireJsResolverParams { base: string; } -TraceSource.get("RequireJsResolver"); +const trace = TraceSource.get("@implab/core/di/RequireJsResolver"); export class RequireJsResolver extends ModuleResolverBase { _contextRequire = require; _base: string; - constructor(opts) { + constructor(opts?: RequireJsResolverParams) { super(); if (opts) { @@ -40,6 +43,8 @@ export class RequireJsResolver extends M async createResolver(moduleName: string): Promise { argumentNotEmptyString(moduleName, "moduleName"); + trace.log("createResolver({0})", moduleName); + const parts = moduleName.split("/"); if (parts[0] === ".") { if (this._base) @@ -55,17 +60,38 @@ export class RequireJsResolver extends M const shim = parts.join("/"); - const contextRequire = await new Promise( - resolve => define(shim, ["require"], resolve) - ); + trace.debug(`define shim ${shim}`); - return new RequireJsResolver({ - base: parts.slice(0, -1).join("/"), - contextRequire - }); + try { + const contextRequire = await new Promise( + (resolve, reject) => { + try { + define(shim, ["require"], r => { + trace.debug("shim resolved"); + resolve(r); + }, reject); + require([shim]); + } catch (e) { + reject(e); + } + } + ); + + trace.debug("creating new moduleResolver"); + + return new RequireJsResolver({ + base: parts.slice(0, -1).join("/"), + contextRequire + }); + } catch (e) { + trace.error(e); + throw e; + } + } async loadModule(moduleName: string): Promise { + trace.log(`loadModule(${moduleName})`); return new Promise( resolve => this._contextRequire.call(null, [moduleName], resolve) ); diff --git a/src/ts/di/ServiceDescriptor.ts b/src/ts/di/ServiceDescriptor.ts --- a/src/ts/di/ServiceDescriptor.ts +++ b/src/ts/di/ServiceDescriptor.ts @@ -37,17 +37,19 @@ function _parse(value, context: Activati if (isPrimitive(value)) return value; + trace.debug("parse {0}", path); + if (isDescriptor(value)) return context.activate(value, path); if (value instanceof Array) - return value.map((x, i) => this._parse(x, context, `${path}[${i}]`)); + return value.map((x, i) => _parse(x, context, `${path}[${i}]`)); const t = {}; for (const p of Object.keys(value)) - t[p] = this._parse(value[p], context, `${path}.${p}`); + t[p] = _parse(value[p], context, `${path}.${p}`); + return t; - } export interface ServiceDescriptorParams { diff --git a/src/ts/di/interfaces.ts b/src/ts/di/interfaces.ts --- a/src/ts/di/interfaces.ts +++ b/src/ts/di/interfaces.ts @@ -62,5 +62,5 @@ export function isValueRegistration(x): } export function isDependencyRegistration(x): x is DependencyRegistration { - return (!isPrimitive(x)) && ("$depdendency" in x); + return (!isPrimitive(x)) && ("$dependency" in x); } diff --git a/src/ts/log/TraceSource.ts b/src/ts/log/TraceSource.ts --- a/src/ts/log/TraceSource.ts +++ b/src/ts/log/TraceSource.ts @@ -1,7 +1,7 @@ -import * as format from '../text/format' -import { argumentNotNull } from '../safe'; -import { Observable } from '../Observable' -import { IDestroyable } from '../interfaces'; +import * as format from "../text/format"; +import { argumentNotNull } from "../safe"; +import { Observable } from "../Observable"; +import { IDestroyable } from "../interfaces"; export const DebugLevel = 400; @@ -16,11 +16,11 @@ export const SilentLevel = 0; export class TraceEvent { readonly source: TraceSource; - readonly level: Number; + readonly level: number; readonly arg: any; - constructor(source: TraceSource, level: Number, arg: any) { + constructor(source: TraceSource, level: number, arg: any) { this.source = source; this.level = level; this.arg = arg; @@ -185,4 +185,3 @@ export class TraceSource { return Registry.instance.get(id); } } - diff --git a/test/js/mock/config1.js b/test/js/mock/config1.js new file mode 100644 --- /dev/null +++ b/test/js/mock/config1.js @@ -0,0 +1,17 @@ +define({ + foo: { + $type: "./Foo#Foo" + }, + + bar: { + $type: "./Bar#Bar", + params: { + db: { + provider: { + $dependency: "db" + } + } + } + }, + db: "db://localhost" +}); \ No newline at end of file diff --git a/test/ts/ContainerTests.ts b/test/ts/ContainerTests.ts --- a/test/ts/ContainerTests.ts +++ b/test/ts/ContainerTests.ts @@ -20,20 +20,31 @@ test("Container register/resolve tests", const connection1 = "db://localhost"; - container.register("connection", new ValueDescriptor(connection1)); + t.throws( + () => container.register("bla-bla", "bla-bla"), + "Do not allow to register anything other than descriptors" + ); - t.equals(container.getService("connection"), connection1); + t.doesNotThrow( + () => container.register("connection", new ValueDescriptor(connection1)), + "register ValueDescriptor" + ); - container.register( - "dbParams", - new AggregateDescriptor({ - timeout: 10, - connection: new ReferenceDescriptor({ name: "connection" }) - }) + t.equals(container.getService("connection"), connection1, "resolve string value"); + + t.doesNotThrow( + () => container.register( + "dbParams", + new AggregateDescriptor({ + timeout: 10, + connection: new ReferenceDescriptor({ name: "connection" }) + }) + ), + "register AggregateDescriptor" ); const dbParams = container.getService("dbParams"); - t.equals(dbParams.connection, connection1, "should get connection"); + t.equals(dbParams.connection, connection1, "should get string value 'dbParams.connection'"); writer.destroy(); }); @@ -53,6 +64,13 @@ test("Container configure/resolve tests" $type: Foo }, + box: { + $type: Bar, + params: { + $dependency: "foo" + } + }, + bar: { $type: Bar, params: { @@ -64,11 +82,37 @@ test("Container configure/resolve tests" } } }); + t.pass("should configure from js object"); const f1 = container.resolve("foo"); + + t.assert(!isNull(f1), "foo should be not null"); + + t.throws(() => container.resolve("bar"), "should not resolve dependency 'db'"); + + writer.destroy(); +}); + +test("Load configuration from module", async t => { + const writer = new TapeWriter(t); + + TraceSource.on(ts => { + ts.level = DebugLevel; + writer.writeEvents(ts.events); + }); + + const container = new Container(); + + await container.configure("test/mock/config1"); + t.pass("The configuration should load"); + + const f1 = container.resolve("foo"); + t.assert(!isNull(f1), "foo should be not null"); const b1 = container.resolve("bar"); + t.assert(!isNull(b1), "foo should be not null"); + writer.destroy(); });