Container.ts
140 lines
| 4.5 KiB
| video/mp2t
|
TypeScriptLexer
|
|
r49 | import { ActivationContext } from "./ActivationContext"; | ||
| import { ValueDescriptor } from "./ValueDescriptor"; | ||||
| import { ActivationError } from "./ActivationError"; | ||||
|
|
r118 | import { ServiceMap, Descriptor, PartialServiceMap, ContainerServices, Resolver } from "./interfaces"; | ||
|
|
r49 | import { TraceSource } from "../log/TraceSource"; | ||
| import { Configuration } from "./Configuration"; | ||||
| import { Cancellation } from "../Cancellation"; | ||||
|
|
r114 | import { MapOf } from "../interfaces"; | ||
|
|
r118 | import { isDescriptor } from "./traits"; | ||
|
|
r49 | |||
| const trace = TraceSource.get("@implab/core/di/ActivationContext"); | ||||
|
|
r115 | export class Container<S = any> implements Resolver<S> { | ||
|
|
r118 | readonly _services: PartialServiceMap<ContainerServices<S>>; | ||
|
|
r49 | |||
|
|
r114 | readonly _cache: MapOf<any>; | ||
| readonly _cleanup: (() => void)[]; | ||||
|
|
r49 | |||
|
|
r114 | readonly _root: Container<S>; | ||
|
|
r49 | |||
|
|
r114 | readonly _parent?: Container<S>; | ||
|
|
r49 | |||
|
|
r114 | _disposed: boolean; | ||
|
|
r49 | |||
|
|
r114 | constructor(parent?: Container<S>) { | ||
|
|
r49 | this._parent = parent; | ||
| this._services = parent ? Object.create(parent._services) : {}; | ||||
| this._cache = {}; | ||||
| this._cleanup = []; | ||||
| this._root = parent ? parent.getRootContainer() : this; | ||||
|
|
r115 | this._services.container = new ValueDescriptor(this) as any; | ||
|
|
r114 | this._disposed = false; | ||
|
|
r49 | } | ||
| getRootContainer() { | ||||
| return this._root; | ||||
| } | ||||
| getParent() { | ||||
| return this._parent; | ||||
| } | ||||
|
|
r115 | resolve<K extends keyof ContainerServices<S>, T extends ContainerServices<S>[K] = ContainerServices<S>[K]>(name: K, def?: T): T { | ||
|
|
r49 | trace.debug("resolve {0}", name); | ||
| const d = this._services[name]; | ||||
| if (d === undefined) { | ||||
|
|
r114 | if (def !== undefined) | ||
|
|
r49 | return def; | ||
| else | ||||
| throw new Error("Service '" + name + "' isn't found"); | ||||
|
|
r114 | } else { | ||
|
|
r49 | |||
|
|
r114 | const context = new ActivationContext<S>(this, this._services); | ||
| try { | ||||
|
|
r115 | return context.activate(d as Descriptor<S, T>, name.toString()); | ||
|
|
r114 | } catch (error) { | ||
| throw new ActivationError(name.toString(), context.getStack(), error); | ||||
| } | ||||
|
|
r49 | } | ||
| } | ||||
| /** | ||||
| * @deprecated use resolve() method | ||||
| */ | ||||
|
|
r115 | getService<K extends keyof S, T extends ContainerServices<S>[K] = ContainerServices<S>[K]>(name: K, def?: T) { | ||
|
|
r114 | return this.resolve(name, def); | ||
|
|
r49 | } | ||
|
|
r115 | register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>): this; | ||
| register(services: PartialServiceMap<S>): this; | ||||
| register<K extends keyof S>(nameOrCollection: K | ServiceMap<S>, service?: Descriptor<S, S[K]>) { | ||||
|
|
r49 | if (arguments.length === 1) { | ||
|
|
r114 | const data = nameOrCollection as ServiceMap<S>; | ||
|
|
r115 | |||
|
|
r114 | for (const name in data) { | ||
| if (Object.prototype.hasOwnProperty.call(data, name)) { | ||||
|
|
r115 | this.register(name, data[name] as Descriptor<S, S[keyof S]>); | ||
|
|
r114 | } | ||
| } | ||||
|
|
r49 | } else { | ||
| if (!isDescriptor(service)) | ||||
| throw new Error("The service parameter must be a descriptor"); | ||||
|
|
r115 | this._services[nameOrCollection as K] = service as any; | ||
|
|
r49 | } | ||
| return this; | ||||
| } | ||||
|
|
r114 | onDispose(callback: () => void) { | ||
|
|
r49 | if (!(callback instanceof Function)) | ||
| throw new Error("The callback must be a function"); | ||||
| this._cleanup.push(callback); | ||||
| } | ||||
| dispose() { | ||||
|
|
r114 | if (this._disposed) | ||
| return; | ||||
| this._disposed = true; | ||||
| for (const f of this._cleanup) | ||||
| f(); | ||||
|
|
r49 | } | ||
| /** | ||||
| * @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. | ||||
| * | ||||
| * @param{Function} opts.contextRequire | ||||
| * The function which will be used to load a configuration or types for services. | ||||
| * | ||||
| */ | ||||
|
|
r59 | async configure(config: string | object, opts?: any, ct = Cancellation.none) { | ||
|
|
r115 | const c = new Configuration<S>(this); | ||
|
|
r49 | |||
| if (typeof (config) === "string") { | ||||
|
|
r59 | return c.loadConfiguration(config, opts && opts.contextRequire, ct); | ||
|
|
r49 | } else { | ||
| return c.applyConfiguration(config, opts && opts.contextRequire, ct); | ||||
| } | ||||
| } | ||||
|
|
r114 | createChildContainer<S2 extends { container?: Container<S & S2> } = S>(): Container<S & S2> { | ||
| return new Container<S & S2>(this as any); | ||||
|
|
r49 | } | ||
|
|
r107 | has(id: string | number) { | ||
|
|
r49 | return id in this._cache; | ||
| } | ||||
|
|
r107 | get(id: string | number) { | ||
|
|
r49 | return this._cache[id]; | ||
| } | ||||
|
|
r107 | store(id: string | number, value: any) { | ||
|
|
r49 | return (this._cache[id] = value); | ||
| } | ||||
| } | ||||
