diff --git a/src/main/ts/ActivationContext.ts b/src/main/ts/ActivationContext.ts --- a/src/main/ts/ActivationContext.ts +++ b/src/main/ts/ActivationContext.ts @@ -29,7 +29,7 @@ export class ActivationContext implem private readonly _service: Descriptor; - private readonly _containerLifetimeManager: ILifetimeManager; + private readonly _lifetimeManagers: ILifetimeManager[]; private readonly _parent: ActivationContext | undefined; @@ -41,12 +41,12 @@ export class ActivationContext implem * @param service the service to activate, this parameter is used for the * debug purpose. */ - constructor(containerLifetimeManager: ILifetimeManager, services: DescriptorMap, name: string, service: Descriptor, cache = {}) { + constructor(lifetimeManagers: ILifetimeManager[], services: DescriptorMap, name: string, service: Descriptor, cache = {}) { this._name = name; this._service = service; this._cache = cache; this._services = services; - this._containerLifetimeManager = containerLifetimeManager; + this._lifetimeManagers = lifetimeManagers; } /** the name of the current resolving dependency */ @@ -54,10 +54,6 @@ export class ActivationContext implem return this._name; } - createContainerLifetime() { - return this._containerLifetimeManager.create(); - } - resolve(name: K): NonNullable; resolve(name: K, def: T): NonNullable | T; resolve(name: K, def?: T): NonNullable | T | undefined { diff --git a/src/main/ts/Container.ts b/src/main/ts/Container.ts --- a/src/main/ts/Container.ts +++ b/src/main/ts/Container.ts @@ -1,32 +1,22 @@ import { ActivationContext } from "./ActivationContext"; import { ActivationError } from "./ActivationError"; import { ContainerBuilder } from "./ContainerBuilder"; +import { LifetimeManager } from "./LifetimeManager"; import { DescriptorMap, IContainerBuilder, IDestroyable, ILifetimeManager, ServiceLocator } from "./interfaces"; export class Container implements ServiceLocator, IDestroyable { private readonly _services: DescriptorMap; - private readonly _lifetimeManager: ILifetimeManager; + private readonly _lifetimeManagers: ILifetimeManager[]; private _disposed: boolean; private readonly _onDestroyed: () => void; - constructor(services: DescriptorMap, lifetimeManager: ILifetimeManager, destroyed: () => void) { - this._services = { - ...services, - container: { - configurable: false, - activate: () => this - }, - childContainer: { - configurable: false, - activate: () => this.createChildBuilder(), - } - }; - + constructor(services: DescriptorMap, lifetimeManagers: ILifetimeManager[], destroyed: () => void) { + this._services = services; this._disposed = false; - this._lifetimeManager = lifetimeManager; + this._lifetimeManagers = lifetimeManagers.concat(new LifetimeManager()); this._onDestroyed = destroyed; } @@ -56,7 +46,7 @@ export class Container implements Ser else throw new Error(`Service '${String(name)}' isn't found`); } else { - const context = new ActivationContext(this._lifetimeManager, this._services, String(name), d); + const context = new ActivationContext(this._lifetimeManagers, this._services, String(name), d); try { return d.activate(context); } catch (error) { diff --git a/src/main/ts/ContainerBuilder.ts b/src/main/ts/ContainerBuilder.ts --- a/src/main/ts/ContainerBuilder.ts +++ b/src/main/ts/ContainerBuilder.ts @@ -1,25 +1,30 @@ import { Container } from "./Container"; import { DescriptorBuilder } from "./DescriptorBuilder"; -import { Descriptor, IContainerBuilder, IDescriptorBuilder, DescriptorMap, ServiceLocator, ILifetime, IDestroyable } from "./interfaces"; +import { containerSelfDescriptor } from "./DescriptorImpl"; +import { Descriptor, IContainerBuilder, IDescriptorBuilder, DescriptorMap, ServiceLocator, ILifetime, IDestroyable, ContainerServices, ContainerServicesConstraint } from "./interfaces"; import { emptyLifetime, LifetimeManager } from "./LifetimeManager"; import { isDestroyable, prototype } from "./traits"; /** * Container builder used to prepare service descriptors and create a IoC container */ -export class ContainerBuilder implements +export class ContainerBuilder, U extends keyof S> implements IContainerBuilder { private _pending = 1; - private readonly _services: DescriptorMap; + private readonly _services: DescriptorMap>; private readonly _lifetimeManager = new LifetimeManager(); private readonly _lifetime: ILifetime; constructor(parentServices: DescriptorMap | null = null, lifetime?: ILifetime) { - this._services = prototype(parentServices); + this._services = { + ...parentServices, + container: containerSelfDescriptor as any, + childContainer: containerSelfDescriptor as any + }; this._lifetimeManager = new LifetimeManager(); this._lifetime = lifetime ?? emptyLifetime(); } diff --git a/src/main/ts/DescriptorImpl.ts b/src/main/ts/DescriptorImpl.ts --- a/src/main/ts/DescriptorImpl.ts +++ b/src/main/ts/DescriptorImpl.ts @@ -13,6 +13,13 @@ export interface DescriptorImplArgs; } +export const containerSelfDescriptor = () => Object.freeze({ + level: 0, + activate(context: IActivationContext) { + return context.createChildContainer(); + } +}); + export class DescriptorImpl implements Descriptor { diff --git a/src/main/ts/LifetimeManager.ts b/src/main/ts/LifetimeManager.ts --- a/src/main/ts/LifetimeManager.ts +++ b/src/main/ts/LifetimeManager.ts @@ -29,12 +29,10 @@ const _emptySlot = Object.freeze({ cleanup: noop, }); -const _destroy = (item: IDestroyable) => item.destroy(); +const _destroy = (item: unknown) => () => isDestroyable(item) && item.destroy() ; const _makeCleanup = (value: T, cleanup?: (item: T) => void) => - cleanup ? () => cleanup(value) : - isDestroyable(value) ? () => _destroy(value) : - noop; + cleanup ? () => cleanup(value) : _destroy(value); const newSlot = (put: (item: ILifetimeSlot) => void, remove: () => void): ILifetimeSlot => ({ has: () => false, @@ -119,6 +117,12 @@ export const emptyLifetime = () => () let nextId = 1; +export const containerLifetime = () => { + const slotId = nextId ++; + return (context: ILifetimeContext) => + context.ownerSlot(slotId); +}; + export const hierarchyLifetime = () => { const slotId = nextId++; return (context: ILifetimeContext) => diff --git a/src/main/ts/interfaces.ts b/src/main/ts/interfaces.ts --- a/src/main/ts/interfaces.ts +++ b/src/main/ts/interfaces.ts @@ -1,3 +1,4 @@ +import { ContainerBuilder } from "./ContainerBuilder"; import { key } from "./traits"; export interface IDestroyable { @@ -157,6 +158,10 @@ export type ContainerServicesConstraint< export interface Descriptor { + /** The level of the service in the containers chain. + */ + readonly level: number; + /** This flags indicates that this registration can be replaced or overridden. */ readonly configurable?: boolean; @@ -170,6 +175,9 @@ export interface Descriptor { /** The context used to initialize lifetime instance {@linkcode ILifetime} */ export interface ILifetimeContext { + + ownerSlot(slotId: string | number): ILifetimeSlot; + contextSlot(slotId: string | number): ILifetimeSlot; containerSlot(slotId: string | number): ILifetimeSlot; @@ -181,6 +189,10 @@ export interface IActivationContext e register(name: K, service: DescriptorMap[K]): void; fail(error: unknown): never; + + selfContainer(): ServiceLocator; + + createChildContainer(): IContainerBuilder>; } /** @@ -196,10 +208,10 @@ export type DescriptorMap = { type ContainerKeys = keyof ContainerProvided; -export type ContainerProvided> = { - container: ServiceLocator>; +export type ContainerProvided = { + container: ServiceLocator>; - childContainer: IContainerBuilder, Exclude>; + childContainer: IContainerBuilder; }; @@ -208,8 +220,8 @@ export type ContainerProvided> = { - [k in keyof S | ContainerKeys]: +export type ContainerServices = { + [k in (keyof S) | ContainerKeys]: k extends ContainerKeys ? ContainerProvided[k] : k extends keyof S ? S[k] : never }; diff --git a/src/test/ts/t/container.ts b/src/test/ts/t/container.ts --- a/src/test/ts/t/container.ts +++ b/src/test/ts/t/container.ts @@ -2,7 +2,7 @@ import { describe, it } from "mocha"; import { Container } from "../Container"; import { ContainerBuilder } from "../ContainerBuilder"; -import { ContainerServices, DepsMap, IContainerBuilder, Refs, Resolver } from "../interfaces"; +import { ContainerServices, DepsMap, IContainerBuilder, Refs, Resolver, ServiceLocator } from "../interfaces"; import { fluent } from "../traits"; class Foo { @@ -48,9 +48,9 @@ if (refs && refs.name === "foo") { refs.default; } -declare const x: ContainerServices; +declare const x: ServiceLocator>; -x.container.resolve("container"); +x.resolve("container").resolve("container"); mmap({