Container.ts
263 lines
| 8.2 KiB
| video/mp2t
|
TypeScriptLexer
|
|
r33 | import { ActivationContext } from "./ActivationContext"; | ||
|
|
r34 | import { ValueDescriptor } from "./ValueDescriptor"; | ||
| import { ActivationError } from "./ActivationError"; | ||||
|
|
r38 | import { isDescriptor, ActivationType, ServiceMap, isDependencyRegistration, isValueRegistration, ServiceRegistration } from "./interfaces"; | ||
|
|
r34 | import { AggregateDescriptor } from "./AggregateDescriptor"; | ||
| import { isPrimitive } from "../safe"; | ||||
| import { ReferenceDescriptor } from "./ReferenceDescriptor"; | ||||
|
|
r38 | import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor"; | ||
| import { ModuleResolverBase } from "./ModuleResolverBase"; | ||||
| import format = require("../text/format"); | ||||
|
|
r33 | |||
| export class Container { | ||||
|
|
r38 | _services: ServiceMap; | ||
|
|
r33 | |||
|
|
r38 | _cache: object; | ||
| _cleanup: (() => void)[]; | ||||
|
|
r33 | |||
|
|
r38 | _root: Container; | ||
|
|
r33 | |||
|
|
r38 | _parent: Container; | ||
|
|
r33 | |||
|
|
r38 | _resolver: ModuleResolverBase; | ||
|
|
r33 | |||
| constructor(parent?: Container) { | ||||
| this._parent = parent; | ||||
| this._services = parent ? Object.create(parent._services) : {}; | ||||
| this._cache = {}; | ||||
| this._cleanup = []; | ||||
| this._root = parent ? parent.getRootContainer() : this; | ||||
| this._services.container = new ValueDescriptor(this); | ||||
| } | ||||
| getRootContainer() { | ||||
| return this._root; | ||||
| } | ||||
| getParent() { | ||||
| return this._parent; | ||||
| } | ||||
| getService<T = any>(name: string, def?: T) { | ||||
|
|
r38 | const d = this._services[name]; | ||
|
|
r33 | if (!d) | ||
| if (arguments.length > 1) | ||||
| return def; | ||||
| else | ||||
| throw new Error("Service '" + name + "' isn't found"); | ||||
| if (d.isInstanceCreated()) | ||||
|
|
r38 | return d.getInstance() as T; | ||
|
|
r33 | |||
|
|
r38 | const context = new ActivationContext(this, this._services); | ||
|
|
r33 | |||
| try { | ||||
|
|
r38 | return d.activate(context, name) as T; | ||
|
|
r33 | } catch (error) { | ||
| throw new ActivationError(name, context.getStack(), error); | ||||
| } | ||||
| } | ||||
| register(nameOrCollection, service?) { | ||||
|
|
r38 | if (arguments.length === 1) { | ||
| const data = nameOrCollection; | ||||
| for (const name in data) | ||||
|
|
r33 | this.register(name, data[name]); | ||
| } else { | ||||
|
|
r34 | if (!(isDescriptor(service))) | ||
|
|
r33 | service = new ValueDescriptor(service); | ||
| this._services[nameOrCollection] = service; | ||||
| } | ||||
| return this; | ||||
| } | ||||
| onDispose(callback) { | ||||
| if (!(callback instanceof Function)) | ||||
| throw new Error("The callback must be a function"); | ||||
| this._cleanup.push(callback); | ||||
| } | ||||
| dispose() { | ||||
| if (this._cleanup) { | ||||
|
|
r38 | for (const f of this._cleanup) | ||
| f(); | ||||
|
|
r33 | this._cleanup = null; | ||
| } | ||||
| } | ||||
| /** | ||||
| * @param{String|Object} config | ||||
| * The configuration of the contaier. Can be either a string or an object, | ||||
| * if the configuration is an object it's treated as a collection of | ||||
| * services which will be registed in the contaier. | ||||
|
|
r38 | * | ||
|
|
r33 | * @param{Function} opts.contextRequire | ||
| * The function which will be used to load a configuration or types for services. | ||||
|
|
r38 | * | ||
|
|
r33 | */ | ||
|
|
r38 | async configure(config: string | object, opts?: object) { | ||
|
|
r33 | if (typeof (config) === "string") { | ||
|
|
r38 | const resolver = await this._resolver.createResolver(config, opts); | ||
| const data = await this._resolver.loadModule(config); | ||||
| return this._configure(data, { resolver }); | ||||
|
|
r33 | } else { | ||
|
|
r38 | return this._configure(config); | ||
|
|
r33 | } | ||
| } | ||||
| createChildContainer() { | ||||
| return new Container(this); | ||||
| } | ||||
| has(id) { | ||||
| return id in this._cache; | ||||
| } | ||||
| get(id) { | ||||
| return this._cache[id]; | ||||
| } | ||||
| store(id, value) { | ||||
| return (this._cache[id] = value); | ||||
| } | ||||
|
|
r38 | async _configure(data: object, opts?: { resolver: ModuleResolverBase }) { | ||
| const resolver = (opts && opts.resolver) || this._resolver; | ||||
| const services: ServiceMap = {}; | ||||
|
|
r33 | |||
|
|
r38 | resolver.beginBatch(); | ||
|
|
r33 | |||
|
|
r38 | async function parse(k) { | ||
| services[k] = await this._parse(data[k], resolver); | ||||
|
|
r33 | } | ||
|
|
r38 | const batch = Object.keys(data).map(parse); | ||
|
|
r33 | |||
|
|
r38 | resolver.completeBatch(); | ||
|
|
r33 | |||
|
|
r38 | await Promise.all(batch); | ||
|
|
r33 | |||
|
|
r38 | this.register(services); | ||
|
|
r33 | } | ||
|
|
r38 | async _parse(registration: any, resolver: ModuleResolverBase) { | ||
| if (isPrimitive(registration) || isDescriptor(registration)) | ||||
| return registration; | ||||
| if (isDependencyRegistration(registration)) { | ||||
|
|
r33 | return new ReferenceDescriptor( | ||
|
|
r38 | registration.$dependency, | ||
| registration.lazy, | ||||
| registration.optional, | ||||
| registration["default"], | ||||
| registration.services && this._parseObject(registration.services, 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); | ||||
|
|
r33 | } | ||
|
|
r38 | return this._parseObject(registration, resolver); | ||
|
|
r33 | } | ||
|
|
r38 | async _parseService(data: ServiceRegistration, resolver: ModuleResolverBase) { | ||
| const opts: ServiceDescriptorParams = { | ||||
| owner: this | ||||
| }; | ||||
|
|
r33 | |||
|
|
r38 | function guard<T>(fn: () => PromiseLike<T>) { | ||
| return fn(); | ||||
|
|
r33 | } | ||
|
|
r38 | 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)); | ||||
| } | ||||
|
|
r33 | |||
| if (data.services) | ||||
|
|
r38 | opts.services = await this._parseObject(data.services, resolver); | ||
| if (data.inject instanceof Array) | ||||
| opts.inject = await Promise.all(data.inject.map(x => this._parseObject(x, resolver))); | ||||
| else | ||||
| opts.inject = this._parseObject(data.inject, resolver); | ||||
|
|
r33 | if (data.params) | ||
|
|
r38 | opts.params = this._parse(data.params, resolver); | ||
|
|
r33 | |||
| if (data.activation) { | ||||
| if (typeof (data.activation) === "string") { | ||||
| switch (data.activation.toLowerCase()) { | ||||
| case "singleton": | ||||
|
|
r38 | opts.activation = ActivationType.Singleton; | ||
|
|
r33 | break; | ||
| case "container": | ||||
|
|
r38 | opts.activation = ActivationType.Container; | ||
|
|
r33 | break; | ||
| case "hierarchy": | ||||
|
|
r38 | opts.activation = ActivationType.Hierarchy; | ||
|
|
r33 | break; | ||
| case "context": | ||||
|
|
r38 | opts.activation = ActivationType.Context; | ||
|
|
r33 | break; | ||
| case "call": | ||||
|
|
r38 | opts.activation = ActivationType.Call; | ||
|
|
r33 | 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); | ||||
| } | ||||
|
|
r38 | _parseObject(data: any, typemap) { | ||
|
|
r33 | if (data.constructor && | ||
| data.constructor.prototype !== Object.prototype) | ||||
| return new ValueDescriptor(data); | ||||
|
|
r38 | const o = {}; | ||
|
|
r33 | |||
|
|
r38 | for (const p in data) | ||
|
|
r33 | o[p] = this._parse(data[p], typemap); | ||
| return o; | ||||
| } | ||||
| _parseArray(data, typemap) { | ||||
| if (data.constructor && | ||||
| data.constructor.prototype !== Array.prototype) | ||||
| return new ValueDescriptor(data); | ||||
|
|
r38 | return data.map(x => this._parse(x, typemap)); | ||
|
|
r33 | } | ||
|
|
r38 | } | ||
