diff --git a/src/main/ts/di/ActivationContext.ts b/src/main/ts/di/ActivationContext.ts --- a/src/main/ts/di/ActivationContext.ts +++ b/src/main/ts/di/ActivationContext.ts @@ -1,17 +1,16 @@ import { TraceSource } from "../log/TraceSource"; import { argumentNotNull, argumentNotEmptyString } from "../safe"; -import { Descriptor, ContainerProvided, ContainerServiceMap, ContainerKeys, ContainerResolve } from "./interfaces"; +import { Descriptor, ContainerServiceMap, ContainerKeys, ContainerResolve } from "./interfaces"; import { Container } from "./Container"; import { MapOf } from "../interfaces"; const trace = TraceSource.get("@implab/core/di/ActivationContext"); -export interface ActivationContextInfo { +export interface ActivationContextInfo { name: string; service: string; - scope: ContainerServiceMap; } export class ActivationContext { @@ -19,32 +18,33 @@ export class ActivationContext; - _stack: ActivationContextInfo[]; - _visited: MapOf; _name: string; - _localized: boolean = false; + _service: Descriptor; - container: Container; + _container: Container; + + _parent: ActivationContext | undefined; - constructor(container: Container, services: ContainerServiceMap, name?: string, cache?: object, visited?: MapOf) { - argumentNotNull(container, "container"); - argumentNotNull(services, "services"); - - this._name = name || ""; - this._visited = visited || {}; - this._stack = []; - this._cache = cache || {}; + constructor(container: Container, services: ContainerServiceMap, name: string, service: Descriptor) { + this._name = name; + this._service = service; + this._visited = {}; + this._cache = {}; this._services = services; - this.container = container; + this._container = container; } getName() { return this._name; } + getContainer() { + return this._container; + } + resolve>(name: K, def?: ContainerResolve) { const d = this._services[name]; @@ -70,16 +70,6 @@ export class ActivationContext( - this.container, - this._services, - this._name, - this._cache, - this._visited - ); - } - has(id: string) { return id in this._cache; } @@ -96,9 +86,8 @@ export class ActivationContext, name: string): this { + const clone = Object.create(this); + clone._name = name; + clone._services = Object.create(this._services); + clone._parent = this; + clone._service = service; + return clone; } } diff --git a/src/main/ts/di/Container.ts b/src/main/ts/di/Container.ts --- a/src/main/ts/di/Container.ts +++ b/src/main/ts/di/Container.ts @@ -1,12 +1,14 @@ import { ActivationContext } from "./ActivationContext"; import { ValueDescriptor } from "./ValueDescriptor"; import { ActivationError } from "./ActivationError"; -import { ServiceMap, Descriptor, PartialServiceMap, ContainerProvided, Resolver, ContainerServiceMap, ContainerKeys, ContainerResolve } from "./interfaces"; +import { ServiceMap, Descriptor, PartialServiceMap, ContainerProvided, Resolver, ContainerServiceMap, ContainerKeys, ContainerResolve, ILifetimeManager } from "./interfaces"; import { TraceSource } from "../log/TraceSource"; import { Configuration, RegistrationMap } from "./Configuration"; import { Cancellation } from "../Cancellation"; import { MapOf, IDestroyable } from "../interfaces"; import { isDescriptor } from "./traits"; +import { LifetimeManager } from "./LifetimeManager"; +import { each } from "../safe"; const trace = TraceSource.get("@implab/core/di/ActivationContext"); @@ -15,6 +17,8 @@ export class Container; + readonly _lifetimeManager: ILifetimeManager; + readonly _cleanup: (() => void)[]; readonly _root: Container; @@ -31,6 +35,7 @@ export class Container>(name: K, def?: ContainerResolve): ContainerResolve { trace.debug("resolve {0}", name); const d = this._services[name]; @@ -51,9 +60,9 @@ export class Container(this, this._services); + const context = new ActivationContext(this, this._services, String(name), d); try { - return context.activate(d, name.toString()); + return d.activate(context); } catch (error) { throw new ActivationError(name.toString(), context.getStack(), error); } @@ -73,11 +82,7 @@ export class Container; - for (const name in data) { - if (Object.prototype.hasOwnProperty.call(data, name)) { - this.register(name, data[name] as Descriptor); - } - } + each(data, (v, k) => this.register(k, v)); } else { if (!isDescriptor(service)) throw new Error("The service parameter must be a descriptor"); @@ -127,17 +132,4 @@ export class Container(): Container { return new Container(this as any); } - - has(id: string | number) { - return id in this._cache; - } - - get(id: string | number) { - return this._cache[id]; - } - - store(id: string | number, value: any) { - return (this._cache[id] = value); - } - } diff --git a/src/main/ts/di/LifetimeManager.ts b/src/main/ts/di/LifetimeManager.ts --- a/src/main/ts/di/LifetimeManager.ts +++ b/src/main/ts/di/LifetimeManager.ts @@ -7,16 +7,35 @@ function safeCall(item: () => void) { try { item(); } catch { - // silence + // silence! } } +const emptyLifetime: ILifetime = { + has() { + return false; + }, + + enter() { + + }, + + get() { + throw new Error("The specified item isn't registered with this lifetime manager"); + }, + + store() { + // does nothing + } + +}; + export class LifetimeManager implements IDestroyable, ILifetimeManager { private _cleanup: (() => void)[] = []; private _cache: MapOf = {}; private _destroyed = false; - initialize(id: string, context: ActivationContext): ILifetime { + initialize(id: string): ILifetime { const self = this; let pending = false; return { @@ -58,12 +77,6 @@ export class LifetimeManager implements }; } - - - - - - destroy() { if (!this._destroyed) { this._destroyed = true; @@ -73,20 +86,56 @@ export class LifetimeManager implements } static readonly empty: ILifetimeManager = { - has() { - return false; + initialize(): ILifetime { + return emptyLifetime; }, + destroy() { + throw new Error("Trying to destroy empty lifetime manager, this is a bug."); + } + + }; + + static readonly hierarchyLifetime: ILifetimeManager = { + initialize(id: string, context: ActivationContext): ILifetime { + return context.getContainer().getLifetimeManager().initialize(id, context); + }, + destroy() { + throw new Error("Trying to destroy hierarchy lifetime manager, this is a bug."); + } + }; - get() { - throw new Error("The specified item isn't registered with this lifetime manager"); + static readonly singletonLifetime: ILifetimeManager = { + initialize(id: string): ILifetime { + return singletonLifetimeManager.initialize(id); }, + destroy() { + throw new Error("Trying to destroy singleton lifetime manager, this is a bug."); + } + }; - register() { - // does nothing + static readonly contextLifetime: ILifetimeManager = { + initialize(id: string, context: ActivationContext): ILifetime { + return { + enter() { + if (context.visit(id)) + throw new Error("Cyclic reference detected"); + }, + get() { + return context.get(id); + }, + has() { + return context.has(id); + }, + store(item: any) { + context.store(id, item); + } + + }; }, - destroy() { throw new Error("Trying to destroy empty lifetime manager, this is a bug."); } }; } + +const singletonLifetimeManager = new LifetimeManager(); diff --git a/src/main/ts/di/ServiceDescriptor.ts b/src/main/ts/di/ServiceDescriptor.ts --- a/src/main/ts/di/ServiceDescriptor.ts +++ b/src/main/ts/di/ServiceDescriptor.ts @@ -1,10 +1,11 @@ import { ActivationContext } from "./ActivationContext"; -import { Descriptor, ServiceMap, PartialServiceMap, ActivationType, ILifetimeManager } from "./interfaces"; +import { Descriptor, ServiceMap, PartialServiceMap, ILifetimeManager } from "./interfaces"; import { argumentNotNull, isPrimitive, keys, isNull } from "../safe"; import { TraceSource } from "../log/TraceSource"; import { isDescriptor } from "./traits"; import { LifetimeManager } from "./LifetimeManager"; import { MatchingMemberKeys } from "../interfaces"; +import { Container } from "./Container"; let cacheId = 0; @@ -61,7 +62,9 @@ export type InjectionSpec = { }; export interface ServiceDescriptorParams { - lifetime: ILifetimeManager; + owner: Container; + + lifetime?: ILifetimeManager; params?: P; @@ -85,8 +88,12 @@ export class ServiceDescriptor; + constructor(opts: ServiceDescriptorParams) { - argumentNotNull(opts, "opts"); + argumentNotNull(opts && opts.owner, "opts.owner"); + + this._owner = opts.owner; if (opts.lifetime) this._lifetime = opts.lifetime; diff --git a/src/main/ts/di/interfaces.ts b/src/main/ts/di/interfaces.ts --- a/src/main/ts/di/interfaces.ts +++ b/src/main/ts/di/interfaces.ts @@ -3,8 +3,6 @@ import { IDestroyable } from "../interfa export interface Descriptor { activate(context: ActivationContext): T; - - clone(): this; } export type ServiceMap = {