import { ActivationContext } from "./ActivationContext"; import { Descriptor, ServiceMap, PartialServiceMap, ILifetimeManager, ILifetime } from "./interfaces"; import { isPrimitive, keys, isNull } from "../safe"; import { TraceSource } from "../log/TraceSource"; import { isDescriptor } from "./traits"; import { LifetimeManager } from "./LifetimeManager"; import { MatchingMemberKeys } from "../interfaces"; const trace = TraceSource.get("@implab/core/di/ActivationContext"); function injectMethod(target: T, method: M, context: ActivationContext, args: A) { const m = target[method]; if (!m || typeof m !== "function") throw new Error("Method '" + method + "' not found"); if (args instanceof Array) return m.apply(target, _parse(args, context, "." + method)); else return m.call(target, _parse(args, context, "." + method)); } function makeCleanupCallback(method: Cleaner) { if (typeof (method) === "function") { return (target: T) => { method(target); }; } else { return (target: T) => { const m = target[method] as any; m.apply(target); }; } } function _parse(value: any, context: ActivationContext, path: string): any { if (isPrimitive(value)) return value as any; trace.debug("parse {0}", path); if (isDescriptor(value)) return context.activate(value, path); if (value instanceof Array) return value.map((x, i) => _parse(x, context, `${path}[${i}]`)) as any; const t: any = {}; keys(value).forEach(p => t[p] = _parse(value[p], context, `${path}.${p}`)); return t; } export type Cleaner = ((x: T) => void) | MatchingMemberKeys<() => void, T>; export type InjectionSpec = { [m in keyof T]?: any; }; export interface ServiceDescriptorParams { lifetime?: ILifetimeManager; params?: P; inject?: InjectionSpec[]; services?: PartialServiceMap; cleanup?: Cleaner; } export class ServiceDescriptor implements Descriptor { _services: ServiceMap; _params: P | undefined; _inject: InjectionSpec[]; _cleanup: ((item: T) => void) | undefined; _lifetimeManager = LifetimeManager.empty; _objectLifetime: ILifetime | undefined; constructor(opts: ServiceDescriptorParams) { if (opts.lifetime) this._lifetimeManager = opts.lifetime; if (!isNull(opts.params)) this._params = opts.params; this._inject = opts.inject || []; this._services = (opts.services || {}) as ServiceMap; if (opts.cleanup) { if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function)) throw new Error( "The cleanup parameter must be either a function or a function name"); this._cleanup = makeCleanupCallback(opts.cleanup); } } activate(context: ActivationContext) { if (!this._objectLifetime) this._objectLifetime = this._lifetimeManager.initialize(context); const lifetime = this._objectLifetime; if (lifetime.has()) { return lifetime.get(); } else { lifetime.enter(); const instance = this._create(context); lifetime.store(instance, this._cleanup); return instance; } } _factory(...params: any[]): T { throw Error("Not implemented"); } _create(context: ActivationContext) { trace.debug(`constructing ${context._name}`); if (this._services) { keys(this._services).forEach(p => context.register(p, this._services[p])); } let instance: T; if (this._params === undefined) { instance = this._factory(); } else if (this._params instanceof Array) { instance = this._factory.apply(this, _parse(this._params, context, "args")); } else { instance = this._factory(_parse(this._params, context, "args")); } if (this._inject) { this._inject.forEach(spec => { for (const m in spec) injectMethod(instance, m, context, spec[m]); }); } return instance; } clone() { return Object.create(this); } }