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 @@ -35,12 +35,11 @@ export class ContainerBuilder lifetime.destroy() : () => void (0); + const container = new Container(this._services, this._lifetimeManager, remove); - const container = new Container(this._services, this._lifetimeManager, detach); - lifetime.store(container); + store(container); return container; } 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 @@ -1,4 +1,3 @@ -import { ActivationError } from "./ActivationError"; import { Descriptor, ILifetime, DepsMap, IActivationContext, DescriptorMap } from "./interfaces"; import { each, key } from "./traits"; @@ -44,10 +43,12 @@ export class DescriptorImpl implem activate(context: IActivationContext): NonNullable { - if (this._lifetime.has()) - return this._lifetime.get(); + const { has, get, initialize, store } = this._lifetime(context); - this._lifetime.initialize(context); + if (has()) + return get(); + + initialize(); if (this._overrides) each(this._overrides, (v, k) => context.register(k, v)); @@ -78,8 +79,11 @@ export class DescriptorImpl implem {}; try { + // call the factory method const instance = (0,this._factory)(refs); - this._lifetime.store(instance, this._cleanup); + + // store the instance + store(instance, this._cleanup); return instance; } catch(err) { context.fail(err); 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 @@ -1,5 +1,4 @@ -import { IActivationContext, ILifetime, ILifetimeContext, ILifetimeManager, ILifetimeSlot } from "./interfaces"; -import { ActivationContext } from "./ActivationContext"; +import { IDestroyable, ILifetimeContext, ILifetimeManager, ILifetimeSlot } from "./interfaces"; import { argumentNotNull, isDestroyable } from "./traits"; const safeCall = (item: () => void) => { @@ -10,9 +9,9 @@ const safeCall = (item: () => void) => { } }; -const noop = () => {}; +const noop = () => { }; -const fail = (message: string) => { +const fail = (message: string) => (): never => { throw new Error(message); }; @@ -25,87 +24,81 @@ const _emptySlot = Object.freeze({ store: noop, - remove: noop -}); - -const _unknonwSlot = Object.freeze({ - has: () => false, + remove: noop, - initialize: () => { - throw new Error("Can't call initialize on the unknown lifetime object"); - }, - get: () => { - throw new Error("The lifetime object isn't initialized"); - }, - store: () => { - throw new Error("Can't store a value in the unknown lifetime object"); - } + cleanup: noop, }); +const _destroy = (item: IDestroyable) => item.destroy(); -const pendingSlot = (store: (item: T) => void) => ({ +const _makeCleanup = (value: T, cleanup?: (item: T) => void) => + cleanup ? () => cleanup(value) : + isDestroyable(value) ? () => _destroy(value) : + noop; + +const newSlot = (put: (item: ILifetimeSlot) => void, remove: () => void): ILifetimeSlot => ({ has: () => false, - get: () => { - throw new Error("The value in this slot doesn't exist"); - }, + initialize: () => put(pendingSlot(put, remove)), + + get: fail("The slot doesn't hold a value"), - initialize: () => { - throw new Error("Cyclic reference detected"); - }, + store: (value, cleanup) => put(valueSlot(value, cleanup, remove)), - store + remove: noop, + + cleanup: noop, }); -const valueSlot = (value: T, cleanup: (item: T) => void) => ({ +const pendingSlot = (put: (item: ILifetimeSlot) => void, remove: () => void): ILifetimeSlot => ({ + has: () => false, + + get: fail("The value in this slot doesn't exist"), + + initialize: fail("Cyclic reference detected"), + + store: (value, cleanup) => put(valueSlot(value, cleanup, remove)), + + remove, + + cleanup: noop +}); + +const valueSlot = (value: T, cleanup: ((item: T) => void) | undefined, remove: () => void) => ({ has: () => true, get: () => value, - initialize: () => { - throw new Error("The slot already has a value"); - }, + initialize: fail("The slot already has a value"), + + store: fail("The slot already has a value"), - store: () => { - throw new Error("The slot already has a value"); - } + cleanup: _makeCleanup(value, cleanup), + + remove: remove }); -const singletons: { [K: string]: unknown } = {}; - export class LifetimeManager implements ILifetimeManager { private _destroyed = false; private readonly _slots: Record> = {}; - slot(cookie: string): ILifetimeSlot { + slot(cookie: string | number): ILifetimeSlot { if (cookie in this._slots) return this._slots[cookie] as ILifetimeSlot; - const store = (item: T, cleanup?: (item: T) => void) => { - this._assertNotDestroyed(); - this._slots[cookie] = valueSlot( - item, - cleanup ?? isDestroyable(item) ? () => item.destroy() : noop - ); - }; - - return { - has: () => false, - get: () => { - throw new Error("The value isn't stored in this slot"); - }, - store, - initialize: () => { - this._assertNotDestroyed(); - this._slots[cookie] = pendingSlot(store); - } - }; + return newSlot(this._put(cookie), this._remove(cookie)); } - remove(cookie: string) { - delete this._slots[cookie]; - } + private readonly _put = (id: string | number) => (slot: ILifetimeSlot) => { + this._assertNotDestroyed(); + this._slots[id] = slot as ILifetimeSlot; + }; + + private readonly _remove = (id: string | number) => () => { + this._assertNotDestroyed(); + delete this._slots[id]; + }; private _assertNotDestroyed() { if (this._destroyed) @@ -116,42 +109,20 @@ export class LifetimeManager implements destroy() { if (!this._destroyed) { this._destroyed = true; - Object.values(this._slots).forEach(({clean}) => ) + Object.values(this._slots).forEach(({ cleanup }) => safeCall(cleanup)); } } } -export const emptyLifetime = (): ILifetime => { - return _emptyLifetime; -}; +export const emptyLifetime = () => () => _emptySlot as ILifetimeSlot; -export const hierarchyLifetime = (): ILifetime => { - // TODO: вот здесь ошибка, при первой активации сервиса будет получен и - // привязан lifetime из дочернего контейнера, при активации через второй - // дочерний контейнера это приведет к ошибке, точнее будет взят экземпляр - // из первого контейнера. - let _lifetime: ILifetime = _unknownLifetime; - return { - initialize(context: ILifetimeContext) { - if (_lifetime !== _unknownLifetime) - throw new Error("Cyclic reference activation detected"); +let nextId = 1; - _lifetime = context.createContainerLifetime(); - }, - get() { - return _lifetime.get(); - }, - has() { - return _lifetime.has(); - }, - store(item: NonNullable, cleanup?: (item: NonNullable) => void) { - return _lifetime.store(item, cleanup); - }, - toString() { - return `[object HierarchyLifetime, has=${String(this.has())}]`; - } - }; +export const hierarchyLifetime = () => { + const slotId = nextId++; + return (context: ILifetimeContext) => + context.containerSlot(slotId); }; /** @@ -163,29 +134,15 @@ export const hierarchyLifetime = (): * * @returns The instance of the lifetime. */ -export const contextLifetime = (): ILifetime => { - let _lifetime: ILifetime = _unknownLifetime; - return { - initialize(context: ILifetimeContext) { - if (_lifetime !== _unknownLifetime) - throw new Error("Cyclic reference detected"); - _lifetime = context.createLifetime(); - }, - get() { - return _lifetime.get(); - }, - has() { - return _lifetime.has(); - }, - store(item: NonNullable) { - _lifetime.store(item); - }, - toString() { - return `[object ContextLifetime, has=${String(this.has())}]`; - } - }; +export const contextLifetime = () => { + const slotId = nextId++; + + return (context: ILifetimeContext) => + context.contextSlot(slotId); }; +const singletons = new LifetimeManager(); + /** * Creates the lifetime for the service which will allow existence only one * instance with the specified {@linkcode typeId}. If there will be created @@ -196,31 +153,10 @@ export const contextLifetime = (): IL * fully qualified class name * @returns The lifetime instance */ -export const singletonLifetime = (typeId: string): ILifetime => { +export const singletonLifetime = (typeId: string) => { argumentNotNull(typeId, "typeId"); - let pending = false; - return { - has() { - return typeId in singletons; - }, - get() { - if (!this.has()) - throw new Error(`The instance ${typeId} doesn't exists`); - return singletons[typeId] as NonNullable; - }, - initialize() { - if (pending) - throw new Error("Cyclic reference detected"); - pending = true; - }, - store(item: NonNullable) { - singletons[typeId] = item; - pending = false; - }, - toString() { - return `[object SingletonLifetime, has=${String(this.has())}, typeId=${typeId}]`; - } - }; + + return () => singletons.slot(typeId); }; /** Creates a lifetime bound to the specified container. Using this lifetime @@ -228,25 +164,7 @@ export const singletonLifetime = (typ * * @param container The container which will manage the lifetime for the service */ -export const containerLifetime = (container: { createLifetime(): ILifetime }) => { - let _lifetime: ILifetime = _unknownLifetime; - return { - initialize() { - if (_lifetime !== _unknownLifetime) - throw new Error("Cyclic reference detected"); - _lifetime = container.createLifetime(); - }, - get() { - return _lifetime.get(); - }, - has() { - return _lifetime.has(); - }, - store(item: NonNullable, cleanup?: (item: NonNullable) => void) { - _lifetime.store(item, cleanup); - }, - toString() { - return `[object ContainerLifetime, has=${String(_lifetime.has())}]`; - } - }; +export const containerLifetime = (manager: ILifetimeManager) => { + const slotId = nextId++; + return () => manager.slot(slotId); }; 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 @@ -170,9 +170,9 @@ export interface Descriptor { /** The context used to initialize lifetime instance {@linkcode ILifetime} */ export interface ILifetimeContext { - contextSlot(slotId: string): ILifetimeSlot; + contextSlot(slotId: string | number): ILifetimeSlot; - containerSlot(slotId: string): ILifetimeSlot; + containerSlot(slotId: string | number): ILifetimeSlot; } @@ -210,8 +210,8 @@ export type ContainerProvided> = { [k in keyof S | ContainerKeys]: - k extends ContainerKeys ? ContainerProvided[k] : - k extends keyof S ? S[k] : never + k extends ContainerKeys ? ContainerProvided[k] : + k extends keyof S ? S[k] : never }; @@ -240,31 +240,24 @@ export type ActivationType = "singleton" * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет * свой собственный объект `ILifetime`, который создается при первой активации */ -export interface ILifetime { - /** Проверяет, что уже создан экземпляр объекта */ - has(): boolean; +export type ILifetime = (context: ILifetimeContext) => ILifetimeSlot>; - get(): NonNullable; +export interface ILifetimeSlot { + readonly has: () => boolean; + + readonly get: () => T; - initialize(context: ILifetimeContext): void; + readonly initialize: () => void; + + readonly store: (item: T, cleanup?: (item: T) => void) => void; - store(item: NonNullable, cleanup?: (item: NonNullable) => void): void; + readonly remove: () => void; - toString(): string; + readonly cleanup: () => void; } -export interface ILifetimeSlot { - has: () => boolean; - - get: () => T; - - initialize: () => void; - - store(item: T, cleanup?: (item: T) => void): void; -} - export interface ILifetimeManager extends IDestroyable { - create(): ILifetime; + slot(id: string | number): ILifetimeSlot; } export type ExtractRequired = { [p in K as (undefined extends T[p] ? never : p)]-?: T[p] };