ServiceDescriptor.ts
157 lines
| 4.4 KiB
| video/mp2t
|
TypeScriptLexer
|
|
r49 | import { ActivationContext } from "./ActivationContext"; | ||
|
|
r129 | import { Descriptor, ServiceMap, PartialServiceMap, ActivationType, ILifetimeManager } from "./interfaces"; | ||
|
|
r118 | import { argumentNotNull, isPrimitive, keys, isNull } from "../safe"; | ||
|
|
r49 | import { TraceSource } from "../log/TraceSource"; | ||
|
|
r118 | import { isDescriptor } from "./traits"; | ||
|
|
r129 | import { LifetimeManager } from "./LifetimeManager"; | ||
| import { MatchingMemberKeys } from "../interfaces"; | ||||
|
|
r49 | |||
| let cacheId = 0; | ||||
| const trace = TraceSource.get("@implab/core/di/ActivationContext"); | ||||
|
|
r120 | function injectMethod<T, M extends keyof T, S extends object, A>(target: T, method: M, context: ActivationContext<S>, args: A) { | ||
|
|
r115 | |||
|
|
r49 | const m = target[method]; | ||
|
|
r115 | if (!m || typeof m !== "function") | ||
|
|
r49 | throw new Error("Method '" + method + "' not found"); | ||
| if (args instanceof Array) | ||||
|
|
r65 | return m.apply(target, _parse(args, context, "." + method)); | ||
|
|
r49 | else | ||
|
|
r65 | return m.call(target, _parse(args, context, "." + method)); | ||
|
|
r49 | } | ||
|
|
r130 | function makeCleanupCallback<T>(method: Cleaner<T>) { | ||
|
|
r129 | if (typeof (method) === "function") { | ||
| return (target: T) => { | ||||
| method(target); | ||||
|
|
r49 | }; | ||
| } else { | ||||
|
|
r129 | return (target: T) => { | ||
|
|
r130 | const m = target[method] as any; | ||
| m.apply(target); | ||||
|
|
r49 | }; | ||
| } | ||||
| } | ||||
|
|
r118 | function _parse(value: any, context: ActivationContext<any>, path: string): any { | ||
|
|
r49 | if (isPrimitive(value)) | ||
|
|
r115 | return value as any; | ||
|
|
r49 | |||
| trace.debug("parse {0}", path); | ||||
| if (isDescriptor(value)) | ||||
| return context.activate(value, path); | ||||
| if (value instanceof Array) | ||||
|
|
r115 | return value.map((x, i) => _parse(x, context, `${path}[${i}]`)) as any; | ||
|
|
r49 | |||
|
|
r115 | const t: any = {}; | ||
| keys(value).forEach(p => t[p] = _parse(value[p], context, `${path}.${p}`)); | ||||
|
|
r49 | |||
| return t; | ||||
| } | ||||
|
|
r129 | export type Cleaner<T> = ((x: T) => void) | MatchingMemberKeys<() => void, T>; | ||
|
|
r115 | |||
| export type InjectionSpec<T> = { | ||||
| [m in keyof T]?: any; | ||||
| }; | ||||
|
|
r120 | export interface ServiceDescriptorParams<S extends object, T, P extends any[]> { | ||
|
|
r129 | lifetime: ILifetimeManager; | ||
|
|
r49 | |||
|
|
r115 | params?: P; | ||
|
|
r49 | |||
|
|
r115 | inject?: InjectionSpec<T>[]; | ||
|
|
r49 | |||
|
|
r115 | services?: PartialServiceMap<S>; | ||
|
|
r49 | |||
|
|
r115 | cleanup?: Cleaner<T>; | ||
|
|
r49 | } | ||
|
|
r120 | export class ServiceDescriptor<S extends object, T, P extends any[]> implements Descriptor<S, T> { | ||
|
|
r115 | _services: ServiceMap<S>; | ||
|
|
r49 | |||
|
|
r115 | _params: P | undefined; | ||
|
|
r49 | |||
|
|
r115 | _inject: InjectionSpec<T>[]; | ||
|
|
r49 | |||
|
|
r129 | _cleanup: ((item: T) => void) | undefined; | ||
|
|
r49 | |||
|
|
r129 | _cacheId = String(++cacheId); | ||
|
|
r49 | |||
|
|
r129 | _lifetime = LifetimeManager.empty; | ||
|
|
r49 | |||
|
|
r115 | constructor(opts: ServiceDescriptorParams<S, T, P>) { | ||
|
|
r49 | argumentNotNull(opts, "opts"); | ||
|
|
r129 | if (opts.lifetime) | ||
| this._lifetime = opts.lifetime; | ||||
|
|
r49 | |||
|
|
r115 | if (!isNull(opts.params)) | ||
|
|
r49 | this._params = opts.params; | ||
|
|
r115 | this._inject = opts.inject || []; | ||
|
|
r49 | |||
|
|
r115 | this._services = (opts.services || {}) as ServiceMap<S>; | ||
|
|
r49 | |||
| 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"); | ||||
|
|
r130 | this._cleanup = makeCleanupCallback(opts.cleanup); | ||
|
|
r49 | } | ||
| } | ||||
|
|
r115 | activate(context: ActivationContext<S>) { | ||
|
|
r129 | const lifetime = this._lifetime.initialize(this._cacheId, context); | ||
|
|
r49 | |||
|
|
r129 | if (lifetime.has()) { | ||
| return lifetime.get(); | ||||
| } else { | ||||
|
|
r130 | lifetime.enter(); | ||
|
|
r129 | const instance = this._create(context); | ||
| lifetime.store(this._cacheId, this._cleanup); | ||||
| return instance; | ||||
|
|
r49 | } | ||
| } | ||||
|
|
r115 | _factory(...params: any[]): T { | ||
|
|
r49 | throw Error("Not implemented"); | ||
| } | ||||
|
|
r115 | _create(context: ActivationContext<S>) { | ||
|
|
r49 | trace.debug(`constructing ${context._name}`); | ||
| if (this._services) { | ||||
|
|
r115 | keys(this._services).forEach(p => context.register(p, this._services[p])); | ||
|
|
r49 | } | ||
|
|
r115 | let instance: T; | ||
|
|
r49 | |||
| 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; | ||||
| } | ||||
|
|
r129 | |||
| clone() { | ||||
| return Object.create(this); | ||||
| } | ||||
|
|
r49 | } | ||
