Configuration.ts
350 lines
| 10.6 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"; | ||||
|
|
r59 | import { makeResolver } from "./ResolverHelper"; | ||
| import { ICancellation } from "../interfaces"; | ||||
|
|
r49 | |||
| const trace = TraceSource.get("@implab/core/di/Configuration"); | ||||
|
|
r113 | async function mapAll(data: any | any[], map?: (v: any, k: keyof any) => any): Promise<any> { | ||
|
|
r49 | 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; | ||||
| } | ||||
| } | ||||
|
|
r59 | export type ModuleResolver = (moduleName: string, ct?: ICancellation) => any; | ||
|
|
r49 | |||
| type _key = string | number; | ||||
| export class Configuration { | ||||
| _hasInnerDescriptors = false; | ||||
| _container: Container; | ||||
| _path: Array<_key>; | ||||
|
|
r113 | _configName: string | undefined; | ||
|
|
r49 | |||
|
|
r113 | _require: ModuleResolver | undefined; | ||
|
|
r49 | |||
| constructor(container: Container) { | ||||
|
|
r76 | argumentNotNull(container, "container"); | ||
|
|
r49 | this._container = container; | ||
| this._path = []; | ||||
| } | ||||
|
|
r59 | async loadConfiguration(moduleName: string, contextRequire?: any, ct = Cancellation.none) { | ||
|
|
r51 | argumentNotEmptyString(moduleName, "moduleName"); | ||
|
|
r59 | |||
|
|
r60 | trace.log( | ||
| "loadConfiguration moduleName={0}, contextRequire={1}", | ||||
| moduleName, | ||||
| contextRequire ? typeof (contextRequire) : "<nil>" | ||||
| ); | ||||
|
|
r59 | |||
| this._configName = moduleName; | ||||
|
|
r113 | const r = await makeResolver(undefined, contextRequire); | ||
|
|
r51 | |||
|
|
r59 | const config = await r(moduleName, ct); | ||
| await this._applyConfiguration( | ||||
| config, | ||||
|
|
r65 | await makeResolver(moduleName, contextRequire), | ||
|
|
r59 | ct | ||
| ); | ||||
|
|
r51 | } | ||
|
|
r65 | async applyConfiguration(data: object, contextRequire?: any, ct = Cancellation.none) { | ||
|
|
r49 | argumentNotNull(data, "data"); | ||
|
|
r65 | await this._applyConfiguration(data, await makeResolver(void (0), contextRequire), ct); | ||
|
|
r59 | } | ||
| async _applyConfiguration(data: object, resolver?: ModuleResolver, ct = Cancellation.none) { | ||||
|
|
r49 | 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); | ||||
| } | ||||
|
|
r113 | _makeError(inner: any) { | ||
|
|
r49 | const e = new ConfigError("Failed to load configuration", inner); | ||
|
|
r113 | e.configName = this._configName || "<inline>"; | ||
|
|
r49 | 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; | ||||
| } | ||||
| } | ||||
|
|
r59 | _loadModule(moduleName: string) { | ||
|
|
r49 | trace.debug("loadModule {0}", moduleName); | ||
|
|
r113 | if (!this._require) | ||
| throw new Error("Module loader isn't specified"); | ||||
|
|
r49 | |||
|
|
r59 | return this._require(moduleName); | ||
|
|
r49 | } | ||
|
|
r113 | async _visitRegistrations(data: any, name: _key) { | ||
|
|
r49 | 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; | ||||
| } | ||||
|
|
r113 | _enter(name: keyof any) { | ||
| this._path.push(name.toString()); | ||||
|
|
r49 | trace.debug(">{0}", name); | ||
| } | ||||
| _leave() { | ||||
| const name = this._path.pop(); | ||||
| trace.debug("<{0}", name); | ||||
| } | ||||
|
|
r113 | async _visit<T extends object>(data: T, name: keyof T) { | ||
|
|
r49 | 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; | ||||
| } | ||||
|
|
r113 | _makeServiceParams<T, P, S>(data: ServiceRegistration<T, P, S>) { | ||
|
|
r49 | const opts: any = { | ||
| owner: this._container | ||||
| }; | ||||
| if (data.services) | ||||
| opts.services = this._visitRegistrations(data.services, "services"); | ||||
| if (data.inject) { | ||||
|
|
r65 | this._enter("inject"); | ||
|
|
r49 | 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; | ||||
| } | ||||
|
|
r113 | async _visitValueRegistration<T>(data: ValueRegistration<T>, name: _key) { | ||
|
|
r49 | this._enter(name); | ||
| const d = data.parse ? new AggregateDescriptor(data.$value) : new ValueDescriptor(data.$value); | ||||
| this._leave(); | ||||
| return d; | ||||
| } | ||||
|
|
r113 | async _visitDependencyRegistration<S, K extends keyof S>(data: DependencyRegistration<S, K>, name: keyof S) { | ||
|
|
r49 | argumentNotEmptyString(data && data.$dependency, "data.$dependency"); | ||
| this._enter(name); | ||||
|
|
r113 | const d = new ReferenceDescriptor<S, K>({ | ||
|
|
r49 | 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; | ||||
| } | ||||
|
|
r113 | async _visitTypeRegistration<T, P, S>(data: TypeRegistration<T, P, S>, name: _key) { | ||
|
|
r49 | 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; | ||||
| } | ||||
|
|
r113 | async _visitFactoryRegistration<T, P, S>(data: FactoryRegistration<T, P, S>, name: _key) { | ||
|
|
r65 | argumentOfType(data.$factory, Function, "data.$factory"); | ||
|
|
r49 | this._enter(name); | ||
| const opts = this._makeServiceParams(data); | ||||
|
|
r65 | opts.factory = data.$factory; | ||
|
|
r49 | |||
| const d = new FactoryServiceDescriptor( | ||||
| await mapAll(opts) | ||||
| ); | ||||
| this._leave(); | ||||
| return d; | ||||
| } | ||||
| } | ||||
