Configuration.ts
348 lines
| 10.3 KiB
| video/mp2t
|
TypeScriptLexer
|
|
r49 | 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 { Container } from "./Container"; | ||||
| import { ReferenceDescriptor } from "./ReferenceDescriptor"; | ||||
| import { TypeServiceDescriptor } from "./TypeServiceDescriptor"; | ||||
| import { FactoryServiceDescriptor } from "./FactoryServiceDescriptor"; | ||||
| import { TraceSource } from "../log/TraceSource"; | ||||
| import { ConfigError } from "./ConfigError"; | ||||
| import { Cancellation } from "../Cancellation"; | ||||
| const trace = TraceSource.get("@implab/core/di/Configuration"); | ||||
|
|
r51 | declare const define; | ||
| declare const require; | ||||
| function hasAmdLoader() { | ||||
| return (typeof define === "function" && define.amd); | ||||
| } | ||||
|
|
r49 | async function mapAll(data: object | any[], map?: (v, k) => any): Promise<any> { | ||
| 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; | ||||
| } | ||||
| } | ||||
|
|
r50 | type Resolver = (qname: string) => any; | ||
|
|
r49 | |||
| type _key = string | number; | ||||
| export class Configuration { | ||||
| _hasInnerDescriptors = false; | ||||
| _container: Container; | ||||
| _path: Array<_key>; | ||||
| _configName: string; | ||||
|
|
r50 | _require: Resolver; | ||
|
|
r49 | |||
| constructor(container: Container) { | ||||
| argumentNotNull(container, container); | ||||
| this._container = container; | ||||
| this._path = []; | ||||
| } | ||||
|
|
r51 | async loadConfiguration(moduleName: string, ct = Cancellation.none) { | ||
| argumentNotEmptyString(moduleName, "moduleName"); | ||||
| // TODO remove the code below somewehere else | ||||
| if (hasAmdLoader()) { | ||||
| // if we have a requirejs loader, use it directly | ||||
| // don't rely on typescript 'import' function | ||||
| const m = await new Promise<any>(cb => require(["./RequireJsHelper"], cb)); | ||||
| const r = m.makeResolver(require); | ||||
| const config = await r(moduleName); | ||||
| return this.applyConfiguration( | ||||
| config, | ||||
| m.makeResolver(await m.createContextRequire(moduleName)) | ||||
| ); | ||||
| } else { | ||||
| throw new Error("This feature is supported only with the amd loader"); | ||||
| } | ||||
| } | ||||
|
|
r50 | async applyConfiguration(data: object, resolver?: Resolver, ct = Cancellation.none) { | ||
|
|
r49 | argumentNotNull(data, "data"); | ||
| trace.log("applyConfiguration"); | ||||
| this._configName = "$"; | ||||
|
|
r50 | if (resolver) | ||
| this._require = resolver; | ||||
|
|
r49 | |||
| 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.debug("loadModule {0}", moduleName); | ||||
|
|
r50 | const m = await this._require(moduleName); | ||
|
|
r49 | |||
| return m; | ||||
| } | ||||
| async _visitRegistrations(data, name: _key) { | ||||
| this._enter(name); | ||||
| if (data.constructor && | ||||
| data.constructor.prototype !== Object.prototype) | ||||
| throw new Error("Configuration must be a simple object"); | ||||
| const o: ServiceMap = {}; | ||||
| const keys = Object.keys(data); | ||||
| 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(); | ||||
| return services; | ||||
| } | ||||
| _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<any> { | ||||
| if (isPrimitive(data) || isDescriptor(data)) | ||||
| return data; | ||||
| if (isDependencyRegistration(data)) { | ||||
| return this._visitDependencyRegistration(data, name); | ||||
| } else if (isValueRegistration(data)) { | ||||
| return this._visitValueRegistration(data, name); | ||||
| } else if (isTypeRegistration(data)) { | ||||
| return this._visitTypeRegistration(data, name); | ||||
| } else if (isFactoryRegistration(data)) { | ||||
| return this._visitFactoryRegistration(data, name); | ||||
| } else if (data instanceof Array) { | ||||
| return this._visitArray(data, name); | ||||
| } | ||||
| return this._visitObject(data, name); | ||||
| } | ||||
| 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")); | ||||
| // 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" } | ||||
| // }; | ||||
| this._leave(); | ||||
| return v; | ||||
| } | ||||
| 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 = this._visitRegistrations(data.services, "services"); | ||||
| if (data.inject) { | ||||
| this._path.push("inject"); | ||||
| opts.inject = mapAll( | ||||
| data.inject instanceof Array ? | ||||
| data.inject : | ||||
| [data.inject], | ||||
| delegate(this, "_visitObject") | ||||
| ); | ||||
| this._leave(); | ||||
| } | ||||
| 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") { | ||||
| 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 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 _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 _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 _visitFactoryRegistration(data: FactoryRegistration, name: _key) { | ||||
| argumentOfType(data.$factory, Function, "data.$type"); | ||||
| this._enter(name); | ||||
| const opts = this._makeServiceParams(data); | ||||
| opts.factory = opts.$factory; | ||||
| const d = new FactoryServiceDescriptor( | ||||
| await mapAll(opts) | ||||
| ); | ||||
| this._leave(); | ||||
| return d; | ||||
| } | ||||
| } | ||||
