import { ActivationContext } from "./ActivationContext"; import { ActivationError } from "./ActivationError"; import { ServiceMap, Descriptor, PartialServiceMap, ContainerServiceMap, ContainerKeys, TypeOfService, ServiceContainer } from "./interfaces"; import { TraceSource } from "../log/TraceSource"; import { Configuration, RegistrationMap } from "./Configuration"; import { Cancellation } from "../Cancellation"; import { ICancellation } from "../interfaces"; import { isDescriptor } from "./traits"; import { LifetimeManager } from "./LifetimeManager"; import { each, isString } from "../safe"; import { ContainerConfiguration, FluentRegistrations } from "./fluent/interfaces"; import { FluentConfiguration } from "./fluent/FluentConfiguration"; const trace = TraceSource.get("@implab/core/di/ActivationContext"); export class Container implements ServiceContainer { readonly _services: ContainerServiceMap; readonly _lifetimeManager: LifetimeManager; readonly _cleanup: (() => void)[]; readonly _root: Container; readonly _parent?: Container; _disposed: boolean; constructor(parent?: Container) { this._parent = parent; this._services = Object.create(parent ? parent._services : null); this._cleanup = []; this._root = parent ? parent.getRootContainer() : this; this._services.container = { activate: () => this }; this._services.childContainer = { activate: () => this.createChildContainer() }; this._disposed = false; this._lifetimeManager = new LifetimeManager(); } getRootContainer() { return this._root; } getParent() { return this._parent; } getLifetimeManager() { return this._lifetimeManager; } resolve>(name: K, def?: TypeOfService): TypeOfService; resolve>(name: K, def: undefined): TypeOfService | undefined; resolve>(name: K, def?: TypeOfService): TypeOfService | undefined { trace.debug("resolve {0}", name); const d = this._services[name]; if (d === undefined) { if (arguments.length > 1) return def; else throw new Error("Service '" + name + "' isn't found"); } else { const context = new ActivationContext(this, this._services, String(name), d); try { return d.activate(context); } catch (error) { throw new ActivationError(name.toString(), context.getStack(), error); } } } /** * @deprecated use resolve() method */ getService>(name: K, def?: TypeOfService) { return arguments.length === 1 ? this.resolve(name) : this.resolve(name, def); } register(name: K, service: Descriptor): this; register(services: PartialServiceMap): this; register(nameOrCollection: K | ServiceMap, service?: Descriptor) { if (arguments.length === 1) { const data = nameOrCollection as ServiceMap; each(data, (v, k) => this.register(k, v)); } else { if (!isDescriptor(service)) throw new Error("The service parameter must be a descriptor"); this._services[nameOrCollection as K] = service as any; } return this; } /** @deprecated use getLifetimeManager() */ onDispose(callback: () => void) { if (!(callback instanceof Function)) throw new Error("The callback must be a function"); this._cleanup.push(callback); } destroy() { return this.dispose(); } dispose() { if (this._disposed) return; this._disposed = true; for (const f of this._cleanup) f(); this._lifetimeManager.destroy(); } /** * @param{String|Object} config * The configuration of the container. 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 registered in the container. * * @param{Function} opts.contextRequire * The function which will be used to load a configuration or types for services. * */ async configure(config: string | RegistrationMap, opts?: { contextRequire: any; baseModule?: string }, ct = Cancellation.none) { const _opts = Object.create(opts || null); if (typeof (config) === "string") { _opts.baseModule = config; const module = await import(config); if (module && module.default && typeof (module.default.apply) === "function") return module.default.apply(this); else return this._applyLegacyConfig(module, _opts, ct); } else { return this._applyLegacyConfig(config, _opts, ct); } } applyConfig(config: Promise<{ default: ContainerConfiguration; }>, ct?: ICancellation): Promise>; applyConfig(config: Promise<{ [p in P]: ContainerConfiguration; }>, prop: P, ct?: ICancellation): Promise>; async applyConfig( config: Promise<{ [p in P | "default"]: ContainerConfiguration; }>, propOrCt?: P | ICancellation, ct?: ICancellation ): Promise> { const mod = await config; let _ct: ICancellation; let _prop: P | "default"; if (isString(propOrCt)) { _prop = propOrCt; _ct = ct || Cancellation.none; } else { _ct = propOrCt || Cancellation.none; _prop = "default"; } return mod[_prop].apply(this, _ct); } async _applyLegacyConfig(config: RegistrationMap, opts: { contextRequire: any; baseModule?: string }, ct = Cancellation.none) { return new Configuration(this).applyConfiguration(config, opts); } async fluent(config: FluentRegistrations, ct = Cancellation.none): Promise { await new FluentConfiguration().register(config).apply(this, ct); return this; } createChildContainer(): Container { return new Container(this as any); } }