# HG changeset patch # User cin # Date 2018-12-09 22:59:41 # Node ID 7a410676c874b2300a2d9c5d4a106982410cae69 # Parent 477f9b6ba67f64336283b8251e0bd696a2fd529a fixes, tests di/configuration now works diff --git a/gradle.properties b/gradle.properties --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ -version=1.1.2 -release=rtm \ No newline at end of file +version=1.2.0 +release=rc \ No newline at end of file diff --git a/src/ts/di/AggregateDescriptor.ts b/src/ts/di/AggregateDescriptor.ts --- a/src/ts/di/AggregateDescriptor.ts +++ b/src/ts/di/AggregateDescriptor.ts @@ -1,6 +1,6 @@ import { Descriptor, isDescriptor } from "./interfaces"; import { ActivationContext } from "./ActivationContext"; -import { isPrimitive } from "util"; +import { isPrimitive } from "../safe"; export class AggregateDescriptor implements Descriptor { _value: object; diff --git a/src/ts/di/ConfigError.ts b/src/ts/di/ConfigError.ts new file mode 100644 --- /dev/null +++ b/src/ts/di/ConfigError.ts @@ -0,0 +1,12 @@ +export class ConfigError extends Error { + inner; + + path: string; + + configName: string; + + constructor(message: string, inner?) { + super(message); + this.inner = inner; + } +} diff --git a/src/ts/di/Configuration.ts b/src/ts/di/Configuration.ts --- a/src/ts/di/Configuration.ts +++ b/src/ts/di/Configuration.ts @@ -1,10 +1,48 @@ -import { ServiceRegistration, TypeRegistration, FactoryRegistration, ServiceMap, Descriptor, isDescriptor, isDependencyRegistration, DependencyRegistration, ValueRegistration, ActivationType, isValueRegistration, isTypeRegistration, isFactoryRegistration } from "./interfaces"; -import { isNullOrEmptyString, argumentNotEmptyString, isPrimitive } from "../safe"; +import { + ServiceRegistration, + TypeRegistration, + FactoryRegistration, + ServiceMap, + isDescriptor, + isDependencyRegistration, + DependencyRegistration, + ValueRegistration, + ActivationType, + isValueRegistration, + isTypeRegistration, + isFactoryRegistration +} from "./interfaces"; + +import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get } from "../safe"; import { AggregateDescriptor } from "./AggregateDescriptor"; import { ValueDescriptor } from "./ValueDescriptor"; -import { ServiceDescriptorParams } from "./ServiceDescriptor"; import { Container } from "./Container"; -import { Constructor } from "../interfaces"; +import { ReferenceDescriptor } from "./ReferenceDescriptor"; +import { TypeServiceDescriptor } from "./TypeServiceDescriptor"; +import { FactoryServiceDescriptor } from "./FactoryServiceDescriptor"; +import { rjs, createContextRequire, RequireFn } from "./RequireJsHelper"; +import { TraceSource } from "../log/TraceSource"; +import { ConfigError } from "./ConfigError"; +import { Cancellation } from "../Cancellation"; + +const trace = TraceSource.get("@implab/core/di/Configuration"); + +async function mapAll(data: object | any[], map?: (v, k) => any): Promise { + if (data instanceof Array) { + return Promise.all(map ? data.map(map) : data); + } else { + const keys = Object.keys(data); + + const o: any = {}; + + await Promise.all(keys.map(async k => { + const v = map ? map(data[k], k) : data[k]; + o[k] = isPromise(v) ? await v : v; + })); + + return o; + } +} interface MapOf { [key: string]: T; @@ -20,8 +58,103 @@ export class Configuration { _path: Array<_key>; + _configName: string; + + _require = rjs; + + constructor(container: Container) { + argumentNotNull(container, container); + this._container = container; + this._path = []; + } + + async loadConfiguration(moduleName: string, ct = Cancellation.none) { + argumentNotEmptyString(moduleName, "moduleName"); + + trace.log("loadConfiguration {0}", moduleName); + + this._configName = moduleName; + + const config = await this._loadModule(moduleName); + + this._require = await this._createContextRequire(moduleName); + + let services: ServiceMap; + + try { + services = await this._visitRegistrations(config, moduleName); + } catch (e) { + throw this._makeError(e); + } + + this._container.register(services); + } + + async applyConfiguration(data: object, contextRequire?: RequireFn, ct = Cancellation.none) { + argumentNotNull(data, "data"); + + trace.log("applyConfiguration"); + + this._configName = "$"; + + if (contextRequire) + this._require = contextRequire; + + let services: ServiceMap; + + try { + services = await this._visitRegistrations(data, "$"); + } catch (e) { + throw this._makeError(e); + } + + this._container.register(services); + } + + _makeError(inner) { + const e = new ConfigError("Failed to load configuration", inner); + e.configName = this._configName; + e.path = this._makePath(); + return e; + } + + _makePath() { + return this._path + .reduce( + (prev, cur) => typeof cur === "number" ? + `${prev}[${cur}]` : + `${prev}.${cur}` + ) + .toString(); + } + + async _resolveType(moduleName: string, localName: string) { + trace.log("resolveType moduleName={0}, localName={1}", moduleName, localName); + try { + const m = await this._loadModule(moduleName); + return localName ? get(localName, m) : m; + } catch (e) { + trace.error("Failed to resolve type moduleName={0}, localName={1}", moduleName, localName); + throw e; + } + } + + async _loadModule(moduleName: string) { + trace.log("loadModule {0}", moduleName); + + const m = await new Promise(fulfill => { + this._require([moduleName], fulfill); + }); + + return m; + } + + _createContextRequire(moduleName: string) { + return createContextRequire(moduleName); + } + async _visitRegistrations(data, name: _key) { - this._path.push(name); + this._enter(name); if (data.constructor && data.constructor.prototype !== Object.prototype) @@ -30,14 +163,24 @@ export class Configuration { const o: ServiceMap = {}; const keys = Object.keys(data); - const res = await Promise.all(keys.map(k => this._visit(data[k], k))); - keys.forEach((k, i) => { - o[k] = isDescriptor(res[i]) ? res[i] : new AggregateDescriptor(res[i]); - }); + const services = await mapAll(data, async (v, k) => { + const d = await this._visit(v, k); + return isDescriptor(d) ? d : new AggregateDescriptor(d); + }) as ServiceMap; + + this._leave(); - this._path.pop(); + return services; + } - return o; + _enter(name: _key) { + this._path.push(name); + trace.debug(">{0}", name); + } + + _leave() { + const name = this._path.pop(); + trace.debug("<{0}", name); } async _visit(data, name: string): Promise { @@ -59,37 +202,64 @@ export class Configuration { return this._visitObject(data, name); } - async _resolveType(moduleName: string, typeName: string): Promise { + async _visitObject(data: object, name: _key) { + if (data.constructor && + data.constructor.prototype !== Object.prototype) + return new ValueDescriptor(data); - } + this._enter(name); + + const v = await mapAll(data, delegate(this, "_visit")); - async _visitObject(data: object, name: _key): Promise { - this._path.push(name); - this._path.pop(); - } + // TODO: handle inline descriptors properly + // const ex = { + // activate(ctx) { + // const value = ctx.activate(this.prop, "prop"); + // // some code + // }, + // // will be turned to ReferenceDescriptor + // prop: { $dependency: "depName" } + // }; - async _visitArray(data: any[], name: _key): Promise { - this._path.push(name); - this._path.pop(); + this._leave(); + return v; } - async _makeServiceParams(data: ServiceRegistration) { - const opts: any = {}; + async _visitArray(data: any[], name: _key) { + if (data.constructor && + data.constructor.prototype !== Array.prototype) + return new ValueDescriptor(data); + + this._enter(name); + + const v = await mapAll(data, delegate(this, "_visit")); + this._leave(); + + return v; + } + + _makeServiceParams(data: ServiceRegistration) { + const opts: any = { + owner: this._container + }; if (data.services) - opts.services = await this._visitRegistrations(data.services, "services"); + opts.services = this._visitRegistrations(data.services, "services"); if (data.inject) { - if (data.inject instanceof Array) { - this._path.push("inject"); - opts.inject = Promise.all(data.inject.map((x, i) => this._visitObject(x, i))); - this._path.pop(); - } else { - opts.inject = [this._visitObject(data.inject, "inject")]; - } + this._path.push("inject"); + opts.inject = mapAll( + data.inject instanceof Array ? + data.inject : + [data.inject], + delegate(this, "_visitObject") + ); + this._leave(); } - if (data.params) - opts.params = await this._visit(data.params, "params"); + if ("params" in data) + opts.params = data.params instanceof Array ? + this._visitArray(data.params, "params") : + this._visit(data.params, "params"); if (data.activation) { if (typeof (data.activation) === "string") { @@ -120,23 +290,64 @@ export class Configuration { if (data.cleanup) opts.cleanup = data.cleanup; + + return opts; + } + + async _visitValueRegistration(data: ValueRegistration, name: _key) { + this._enter(name); + const d = data.parse ? new AggregateDescriptor(data.$value) : new ValueDescriptor(data.$value); + this._leave(); + return d; } - async _visitValueRegistration(item: ValueRegistration, name: _key) { - this._path.push(name); - this._path.pop(); + async _visitDependencyRegistration(data: DependencyRegistration, name: _key) { + argumentNotEmptyString(data && data.$dependency, "data.$dependency"); + this._enter(name); + const d = new ReferenceDescriptor({ + name: data.$dependency, + lazy: data.lazy, + optional: data.optional, + default: data.default, + services: data.services && await this._visitRegistrations(data.services, "services") + }); + this._leave(); + return d; } - async _visitDependencyRegistration(item: DependencyRegistration, name: _key) { - this._path.push(name); - this._path.pop(); + async _visitTypeRegistration(data: TypeRegistration, name: _key) { + argumentNotNull(data.$type, "data.$type"); + this._enter(name); + + const opts = this._makeServiceParams(data); + if (data.$type instanceof Function) { + opts.type = data.$type; + } else { + const [moduleName, typeName] = data.$type.split(":", 2); + opts.type = this._resolveType(moduleName, typeName); + } + + const d = new TypeServiceDescriptor( + await mapAll(opts) + ); + + this._leave(); + + return d; } - async _visitTypeRegistration(item: TypeRegistration, name: _key) { - argumentNotEmptyString(item.$type, "item.$type"); - } + async _visitFactoryRegistration(data: FactoryRegistration, name: _key) { + argumentOfType(data.$factory, Function, "data.$type"); + this._enter(name); + + const opts = this._makeServiceParams(data); + opts.factory = opts.$factory; - async _visitFactoryRegistration(item: FactoryRegistration, name: _key) { - argumentNotEmptyString(item.$factory, "item.$type"); + const d = new FactoryServiceDescriptor( + await mapAll(opts) + ); + + this._leave(); + return d; } } 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,15 +1,10 @@ import { ActivationContext } from "./ActivationContext"; import { ValueDescriptor } from "./ValueDescriptor"; import { ActivationError } from "./ActivationError"; -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 { isDescriptor, ServiceMap } from "./interfaces"; import { TraceSource } from "../log/TraceSource"; -import { RequireJsResolver } from "./RequireJsResolver"; +import { Configuration } from "./Configuration"; +import { Cancellation } from "../Cancellation"; const trace = TraceSource.get("@implab/core/di/ActivationContext"); @@ -24,8 +19,6 @@ export class Container { _parent: Container; - _resolver: ModuleResolverBase; - constructor(parent?: Container) { this._parent = parent; this._services = parent ? Object.create(parent._services) : {}; @@ -33,7 +26,6 @@ export class Container { this._cleanup = []; this._root = parent ? parent.getRootContainer() : this; this._services.container = new ValueDescriptor(this); - this._resolver = new RequireJsResolver(); } getRootContainer() { @@ -45,6 +37,7 @@ export class Container { } resolve(name: string, def?) { + trace.debug("resolve {0}", name); const d = this._services[name]; if (d === undefined) { if (arguments.length > 1) @@ -64,7 +57,7 @@ export class Container { /** * @deprecated use resolve() method */ - getService(name: string, def?) { + getService() { return this.resolve.apply(this, arguments); } @@ -106,15 +99,13 @@ export class Container { * The function which will be used to load a configuration or types for services. * */ - async configure(config: string | object, opts?: object) { + async configure(config: string | object, opts?: any, ct = Cancellation.none) { + const c = new Configuration(this); + 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 }); + return c.loadConfiguration(config, ct); } else { - trace.log(`json configuration`); - return this._configure(config); + return c.applyConfiguration(config, opts && opts.contextRequire, ct); } } @@ -134,158 +125,4 @@ export class Container { return (this._cache[id] = value); } - async _configure(data: object, opts?: { resolver: ModuleResolverBase }) { - const resolver = (opts && opts.resolver) || this._resolver; - - const services = await this._parseRegistrations(data, resolver); - - this.register(services); - } - - async _parse(data: any, resolver: ModuleResolverBase) { - if (isPrimitive(data) || isDescriptor(data)) - return data; - - 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(data, resolver); - } - - 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, - optional: registration.optional, - default: registration.default, - services: registration.services && await this._parseRegistrations(registration.services, resolver) - }); - } - - async _makeServiceDescriptor(data: ServiceRegistration, resolver: ModuleResolverBase) { - const opts: ServiceDescriptorParams = { - owner: this - }; - - if (data.$type) { - if (data.$type instanceof Function) - opts.type = data.$type; - else if (typeof data.$type === "string") - opts.type = await resolver.resolve(data.$type); - else - throw new Error(format("Unsupported type specification: {0:json}", data.$type)); - } else { - if (data.$factory instanceof Function) - opts.factory = data.$factory; - else if (typeof data.$factory === "string") - opts.factory = await resolver.resolve(data.$factory); - else - throw new Error(format("Unsupported factory specification: {0:json}", data.$factory)); - } - - if (data.services) - opts.services = await this._parseRegistrations(data.services, resolver); - - if (data.inject) { - if (data.inject instanceof Array) - opts.inject = await Promise.all(data.inject.map(x => this._parseObject(x, resolver))); - else - opts.inject = [await this._parseObject(data.inject, resolver)]; - } - - if (data.params) - opts.params = await this._parse(data.params, resolver); - - if (data.activation) { - if (typeof (data.activation) === "string") { - switch (data.activation.toLowerCase()) { - case "singleton": - opts.activation = ActivationType.Singleton; - break; - case "container": - opts.activation = ActivationType.Container; - break; - case "hierarchy": - opts.activation = ActivationType.Hierarchy; - break; - case "context": - opts.activation = ActivationType.Context; - break; - case "call": - opts.activation = ActivationType.Call; - break; - default: - throw new Error("Unknown activation type: " + - data.activation); - } - } else { - opts.activation = Number(data.activation); - } - } - - if (data.cleanup) - opts.cleanup = data.cleanup; - - return new ServiceDescriptor(opts); - } - - async _parseObject(data: object, resolver: ModuleResolverBase) { - if (data.constructor && - data.constructor.prototype !== Object.prototype) - return new ValueDescriptor(data); - - const o = {}; - - for (const p in data) - o[p] = await this._parse(data[p], resolver); - - // TODO: handle inline descriptors properly - // const ex = { - // activate(ctx) { - // const value = ctx.activate(this.prop, "prop"); - // // some code - // }, - - // // will be turned to ReferenceDescriptor - // prop: { $dependency: "depName" } - // }; - - return o; - } - - async _parseArray(data: Array, resolver: ModuleResolverBase) { - if (data.constructor && - data.constructor.prototype !== Array.prototype) - return new ValueDescriptor(data); - - return pmap(data, x => this._parse(x, resolver)); - } - - async _parseRegistrations(data: object, resolver: ModuleResolverBase) { - if (data.constructor && - data.constructor.prototype !== Object.prototype) - throw new Error("Registrations must be a simple object"); - - const o: ServiceMap = {}; - - for (const p of Object.keys(data)) { - const v = await this._parse(data[p], resolver); - o[p] = isDescriptor(v) ? v : new AggregateDescriptor(v); - } - - return o; - } } diff --git a/src/ts/di/FactoryServiceDescriptor.ts b/src/ts/di/FactoryServiceDescriptor.ts new file mode 100644 --- /dev/null +++ b/src/ts/di/FactoryServiceDescriptor.ts @@ -0,0 +1,23 @@ +import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor"; +import { Factory } from "../interfaces"; +import { argumentNotNull, oid } from "../safe"; +import { ActivationType } from "./interfaces"; + +export interface FactoryServiceDescriptorParams extends ServiceDescriptorParams { + factory: Factory; +} + +export class FactoryServiceDescriptor extends ServiceDescriptor { + constructor(opts: FactoryServiceDescriptorParams) { + super(opts); + + argumentNotNull(opts && opts.factory, "opts.factory"); + + // bind to null + this._factory = () => opts.factory(); + + if (opts.activation === ActivationType.Singleton) { + this._cacheId = oid(opts.factory); + } + } +} diff --git a/src/ts/di/ModuleResolverBase.ts b/src/ts/di/ModuleResolverBase.ts deleted file mode 100644 --- a/src/ts/di/ModuleResolverBase.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { argumentNotEmptyString, get } from "../safe"; - -export abstract class ModuleResolverBase { - - async resolve(typeName: string) { - argumentNotEmptyString(typeName, "typeName"); - const [moduleName, localName] = typeName.split("#", 2); - - const moduleObject = await this.loadModule(moduleName); - return localName ? get(localName, moduleObject) : moduleObject; - } - - beginBatch() { - } - - completeBatch() { - } - - abstract loadModule(moduleName: string): PromiseLike; - - abstract createResolver(moduleName: string, opts?: object): PromiseLike; -} diff --git a/src/ts/di/RequireJsResolver.ts b/src/ts/di/RequireJsHelper.ts rename from src/ts/di/RequireJsResolver.ts rename to src/ts/di/RequireJsHelper.ts --- a/src/ts/di/RequireJsResolver.ts +++ b/src/ts/di/RequireJsHelper.ts @@ -1,100 +1,46 @@ -import { ModuleResolverBase } from "./ModuleResolverBase"; import { Uuid } from "../Uuid"; import { argumentNotEmptyString } from "../safe"; import { TraceSource } from "../log/TraceSource"; -type RequireFn = (modules: string[], cb?: (...args: any[]) => any) => void; +export interface RequireFn { + (module: string): any; + (modules: string[], cb?: (...args: any[]) => any): void; +} declare const require: RequireFn; +export const rjs = require; + 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; - - base: string; + contextRequire: RequireFn; } -const trace = TraceSource.get("@implab/core/di/RequireJsResolver"); - -export class RequireJsResolver extends ModuleResolverBase { - _contextRequire = require; - - _base: string; - - constructor(opts?: RequireJsResolverParams) { - super(); - - if (opts) { - - if (opts.contextRequire) - this._contextRequire = opts.contextRequire; +const trace = TraceSource.get("@implab/core/di/RequireJsHelper"); - if (opts.base) { - if (opts.base.indexOf("./") === 0) - throw new Error(`A module id should be an absolute: '${opts.base}'`); - this._base = opts.base; - } - } - - } +export async function createContextRequire(moduleName: string): Promise { + argumentNotEmptyString(moduleName, "moduleName"); - async createResolver(moduleName: string): Promise { - argumentNotEmptyString(moduleName, "moduleName"); - - trace.log("createResolver({0})", moduleName); - - const parts = moduleName.split("/"); - if (parts[0] === ".") { - if (this._base) - parts[0] = this._base; - else - throw new Error(`Can't resolve a relative module '${moduleName}'`); - } + const parts = moduleName.split("/"); + if (parts[0] === ".") + throw new Error("An absolute module path is required"); - if (parts.length > 1) - parts.splice(-1, 1, Uuid()); - else - parts.push(Uuid()); + if (parts.length > 1) + parts.splice(-1, 1, Uuid()); + else + parts.push(Uuid()); - const shim = parts.join("/"); + const shim = parts.join("/"); - trace.debug(`define shim ${shim}`); + trace.debug(`define shim ${shim}`); - 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) - ); - } - + return new Promise(fulfill => { + define(shim, ["require"], r => { + trace.debug("shim resolved"); + return r; + }); + require([shim], fulfill); + }); } 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 @@ -1,8 +1,7 @@ import { ActivationContext } from "./ActivationContext"; import { Descriptor, ActivationType, ServiceMap, isDescriptor } from "./interfaces"; import { Container } from "./Container"; -import { argumentNotNull, isPrimitive, oid, isPromise } from "../safe"; -import { Constructor, Factory } from "../interfaces"; +import { argumentNotNull, isPrimitive } from "../safe"; import { TraceSource } from "../log/TraceSource"; let cacheId = 0; @@ -57,10 +56,6 @@ export interface ServiceDescriptorParams owner: Container; - type?: Constructor; - - factory?: Factory; - params?; inject?: object[]; @@ -79,10 +74,6 @@ export class ServiceDescriptor implement _services: ServiceMap; - _type: Constructor = null; - - _factory: Factory = null; - _params; _inject: object[]; @@ -99,16 +90,9 @@ export class ServiceDescriptor implement this._owner = opts.owner; - if (!(opts.type || opts.factory)) - throw new Error( - "Either a type or a factory must be specified"); - if (opts.activation) this._activationType = opts.activation; - if (opts.type) - this._type = opts.type; - if (opts.params) this._params = opts.params; @@ -118,9 +102,6 @@ export class ServiceDescriptor implement if (opts.services) this._services = opts.services; - if (opts.factory) - this._factory = opts.factory; - if (opts.cleanup) { if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function)) throw new Error( @@ -128,24 +109,16 @@ export class ServiceDescriptor implement this._cleanup = opts.cleanup; } - - if (this._activationType === ActivationType.Singleton) { - const tof = this._type || this._factory; - - // create the persistent cache identifier for the type - if (isPrimitive(tof)) - this._cacheId = tof; - else - this._cacheId = oid(tof); - } else { - this._cacheId = ++cacheId; - } } activate(context: ActivationContext) { // if we have a local service records, register them first let instance; + // ensure we have a cache id + if (!this._cacheId) + this._cacheId = ++cacheId; + switch (this._activationType) { case ActivationType.Singleton: // SINGLETON // if the value is cached return it @@ -224,6 +197,10 @@ export class ServiceDescriptor implement return this._instance; } + _factory(...params: any[]): any { + throw Error("Not implemented"); + } + _create(context: ActivationContext) { trace.debug(`constructing ${context._name}`); @@ -238,21 +215,6 @@ export class ServiceDescriptor implement let instance; - if (!this._factory) { - const ctor = this._type; - if (this._params && this._params.length) { - this._factory = (...args) => { - const t = Object.create(ctor.prototype); - const inst = ctor.apply(t, args); - return isPrimitive(inst) ? t : inst; - }; - } else { - this._factory = () => { - return new ctor(); - }; - } - } - if (this._params === undefined) { instance = this._factory(); } else if (this._params instanceof Array) { @@ -270,19 +232,4 @@ export class ServiceDescriptor implement return instance; } - - // @constructor {singleton} foo/bar/Baz - // @factory {singleton} - toString() { - const parts = []; - - parts.push(this._type ? "@constructor" : "@factory"); - - parts.push(ActivationType[this._activationType]); - - if (typeof (this._type) === "string") - parts.push(this._type); - - return parts.join(" "); - } } diff --git a/src/ts/di/TypeServiceDescriptor.ts b/src/ts/di/TypeServiceDescriptor.ts new file mode 100644 --- /dev/null +++ b/src/ts/di/TypeServiceDescriptor.ts @@ -0,0 +1,36 @@ +import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor"; +import { Constructor, Factory } from "../interfaces"; +import { argumentNotNull, isPrimitive } from "../safe"; + +export interface TypeServiceDescriptorParams extends ServiceDescriptorParams { + type: Constructor; +} + +export class TypeServiceDescriptor extends ServiceDescriptor { + _type: Constructor; + + constructor(opts: TypeServiceDescriptorParams) { + super(opts); + argumentNotNull(opts && opts.type, "opts.type"); + + const ctor = this._type = opts.type; + + if (this._params && this._params.length) { + this._factory = (...args) => { + const t = Object.create(ctor.prototype); + const inst = ctor.apply(t, args); + return isPrimitive(inst) ? t : inst; + }; + } else { + this._factory = () => { + return new ctor(); + }; + } + + } + + toString() { + // @constructor {singleton} foo/bar/Baz + return ``; + } +} diff --git a/src/ts/safe.ts b/src/ts/safe.ts --- a/src/ts/safe.ts +++ b/src/ts/safe.ts @@ -1,5 +1,7 @@ let _nextOid = 0; -const _oid = typeof Symbol === "function" ? Symbol("__oid") : "__oid"; +const _oid = typeof Symbol === "function" ? + Symbol("__implab__oid__") : + "__implab__oid__"; export function oid(instance: object): string { if (isNull(instance)) diff --git a/test/js/mock/config1.js b/test/js/mock/config1.js --- a/test/js/mock/config1.js +++ b/test/js/mock/config1.js @@ -1,10 +1,10 @@ define({ foo: { - $type: "./Foo#Foo" + $type: "./Foo:Foo" }, bar: { - $type: "./Bar#Bar", + $type: "./Bar:Bar", params: { db: { provider: { diff --git a/test/ts/ContainerTests.ts b/test/ts/ContainerTests.ts --- a/test/ts/ContainerTests.ts +++ b/test/ts/ContainerTests.ts @@ -9,13 +9,6 @@ import { Bar } from "./mock/Bar"; import { isNull } from "@implab/core/safe"; test("Container register/resolve tests", async t => { - const writer = new TapeWriter(t); - - TraceSource.on(ts => { - ts.level = DebugLevel; - writer.writeEvents(ts.events); - }); - const container = new Container(); const connection1 = "db://localhost"; @@ -30,7 +23,7 @@ test("Container register/resolve tests", "register ValueDescriptor" ); - t.equals(container.getService("connection"), connection1, "resolve string value"); + t.equals(container.resolve("connection"), connection1, "resolve string value"); t.doesNotThrow( () => container.register( @@ -43,19 +36,11 @@ test("Container register/resolve tests", "register AggregateDescriptor" ); - const dbParams = container.getService("dbParams"); + const dbParams = container.resolve("dbParams"); t.equals(dbParams.connection, connection1, "should get string value 'dbParams.connection'"); - - writer.destroy(); }); test("Container configure/resolve tests", async t => { - const writer = new TapeWriter(t); - - TraceSource.on(ts => { - ts.level = DebugLevel; - writer.writeEvents(ts.events); - }); const container = new Container(); @@ -90,17 +75,9 @@ test("Container configure/resolve tests" 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"); @@ -113,6 +90,4 @@ test("Load configuration from module", a const b1 = container.resolve("bar"); t.assert(!isNull(b1), "foo should be not null"); - - writer.destroy(); }); diff --git a/test/ts/TestTraits.ts b/test/ts/TestTraits.ts --- a/test/ts/TestTraits.ts +++ b/test/ts/TestTraits.ts @@ -1,6 +1,6 @@ import { IObservable, ICancellation, IDestroyable } from "@implab/core/interfaces"; import { Cancellation } from "@implab/core/Cancellation"; -import { TraceEvent, LogLevel, WarnLevel } from "@implab/core/log/TraceSource"; +import { TraceEvent, LogLevel, WarnLevel, DebugLevel, TraceSource } from "@implab/core/log/TraceSource"; import * as tape from "tape"; import { argumentNotNull } from "@implab/core/safe"; @@ -23,12 +23,14 @@ export class TapeWriter implements IDest } writeEvent(next: TraceEvent) { - if (next.level >= LogLevel) { - this._tape.comment("LOG " + next.arg); + if (next.level >= DebugLevel) { + this._tape.comment(`DEBUG ${next.source.id} ${next.arg}`); + } else if (next.level >= LogLevel) { + this._tape.comment(`LOG ${next.source.id} ${next.arg}`); } else if (next.level >= WarnLevel) { - this._tape.comment("WARN " + next.arg); + this._tape.comment(`WARN ${next.source.id} ${next.arg}`); } else { - this._tape.comment("ERROR " + next.arg); + this._tape.comment(`ERROR ${next.source.id} ${next.arg}`); } } @@ -63,6 +65,13 @@ export async function delay(timeout: num export function test(name: string, cb: (t: tape.Test) => any) { tape(name, async t => { + const writer = new TapeWriter(t); + + TraceSource.on(ts => { + ts.level = DebugLevel; + writer.writeEvents(ts.events); + }); + try { await cb(t); } catch (e) { @@ -74,6 +83,7 @@ export function test(name: string, cb: ( } finally { t.end(); + writer.destroy(); } }); }