# HG changeset patch # User cin # Date 2023-09-03 20:50:21 # Node ID dd37d4287c4527774275f9fe50259ea92379c856 # Parent 99a53d63fd6f14a71cf5c77f0676a6f45cb61275 WIP lifetime services, change target to ES2018 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 @@ -1,5 +1,6 @@ +import { ActivationError } from "./ActivationError"; import { Descriptor, ILifetime, IActivationContext, DescriptorMap, ILifetimeManager } from "./interfaces"; -import { argumentNotNull } from "./traits"; +import { argumentNotNull, prototype } from "./traits"; export interface ActivationContextInfo { name: string; @@ -110,7 +111,14 @@ export class ActivationContext implem // if (trace.isLogEnabled()) // trace.log("enter {0} {1}", name, d); - const ctx = this.enter(d, name); + const ctx = new ActivationContext( + this._containerLifetimeManager, + d.hasOverrides ? prototype(this._services) : this._services, + name, + d, + this._cache + ); + const v = d.activate(ctx); // if (trace.isLogEnabled()) @@ -130,15 +138,8 @@ export class ActivationContext implem stack; } - private enter(service: Descriptor, name: string) { - return new ActivationContext( - this._containerLifetimeManager, - service.hasOverrides ? - Object.create(this._services) as typeof this._services : - this._services, - name, - service, - this._cache - ); + fail(innerException: unknown): never { + throw new ActivationError(this._name, this.getStack(), innerException); } + } 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 @@ -2,7 +2,7 @@ import { Container } from "./Container"; import { DescriptorBuilder } from "./DescriptorBuilder"; import { Descriptor, IContainerBuilder, IDescriptorBuilder, DescriptorMap, ServiceLocator, ILifetime, IDestroyable } from "./interfaces"; import { emptyLifetime, LifetimeManager } from "./LifetimeManager"; -import { isDestroyable } from "./traits"; +import { isDestroyable, prototype } from "./traits"; /** * Container builder used to prepare service descriptors and create a IoC container @@ -18,8 +18,8 @@ export class ContainerBuilder; - constructor(parentServices?: DescriptorMap, lifetime?: ILifetime) { - this._services = Object.create(parentServices ? parentServices : null) as DescriptorMap; + constructor(parentServices: DescriptorMap | null = null, lifetime?: ILifetime) { + this._services = prototype(parentServices); 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 @@ -1,3 +1,4 @@ +import { ActivationError } from "./ActivationError"; import { Descriptor, ILifetime, DepsMap, IActivationContext, DescriptorMap } from "./interfaces"; import { each, key } from "./traits"; @@ -53,13 +54,19 @@ export class DescriptorImpl implem const resolve = ({ name, lazy, ...opts }: { name: K; lazy?: boolean; default?: S[K] | null; }) => { if (lazy) { - return () => "default" in opts ? context.resolve(name, opts.default) : context.resolve(name); + return "default" in opts ? + () => context.resolve(name, opts.default) : + () => context.resolve(name); } else { - return "default" in opts ? context.resolve(name, opts.default) : context.resolve(name); + return "default" in opts ? + context.resolve(name, opts.default) : + context.resolve(name); } }; - const makeRefs = (deps: typeof this._deps) => deps ? + const deps = this._deps; + + const refs = deps ? Object.keys(deps) .map(k => { const ref = deps[k]; @@ -67,14 +74,16 @@ export class DescriptorImpl implem { [k]: resolve({ name: ref }) } : { [k]: resolve(ref) }; }) - .reduce((a, p) => ({ ...a, ...p }), {} ): + .reduce((a, p) => ({ ...a, ...p }), {}) : {}; - const instance = this._factory.call(undefined, makeRefs(this._deps)); - - this._lifetime.store(instance, this._cleanup); - - return instance; + try { + const instance = (0,this._factory)(refs); + this._lifetime.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 @@ -10,16 +10,22 @@ const safeCall = (item: () => void) => { } }; +const noop = () => {}; + +const fail = (message: string) => { + throw new Error(message); +}; + const _emptySlot = Object.freeze({ has: () => false, - initialize: () => void (0), + initialize: noop, + + get: fail("The specified item isn't registered with a lifetime manager"), - get: () => { - throw new Error("The specified item isn't registered with this lifetime manager"); - }, + store: noop, - store: () => void (0) + remove: noop }); const _unknonwSlot = Object.freeze({ @@ -36,10 +42,6 @@ const _unknonwSlot = Object.freeze({ } }); -let nextId = 0; - -const singletons: { [K: string]: unknown } = {}; - const pendingSlot = (store: (item: T) => void) => ({ has: () => false, @@ -55,8 +57,6 @@ const pendingSlot = (store: (item: T) store }); -const noop = () => void(0); - const valueSlot = (value: T, cleanup: (item: T) => void) => ({ has: () => true, @@ -71,6 +71,8 @@ const valueSlot = (value: T, cleanup: } }); +const singletons: { [K: string]: unknown } = {}; + export class LifetimeManager implements ILifetimeManager { private _destroyed = false; @@ -101,6 +103,10 @@ export class LifetimeManager implements }; } + remove(cookie: string) { + delete this._slots[cookie]; + } + private _assertNotDestroyed() { if (this._destroyed) throw new Error("The lifetime manager is destroyed"); @@ -110,8 +116,7 @@ export class LifetimeManager implements destroy() { if (!this._destroyed) { this._destroyed = true; - this._cleanup.forEach(safeCall); - this._cleanup.length = 0; + Object.values(this._slots).forEach(({clean}) => ) } } @@ -122,6 +127,10 @@ export const emptyLifetime = (): ILif }; export const hierarchyLifetime = (): ILifetime => { + // TODO: вот здесь ошибка, при первой активации сервиса будет получен и + // привязан lifetime из дочернего контейнера, при активации через второй + // дочерний контейнера это приведет к ошибке, точнее будет взят экземпляр + // из первого контейнера. let _lifetime: ILifetime = _unknownLifetime; return { initialize(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 @@ -170,15 +170,17 @@ export interface Descriptor { /** The context used to initialize lifetime instance {@linkcode ILifetime} */ export interface ILifetimeContext { - createLifetime(): ILifetime; + contextSlot(slotId: string): ILifetimeSlot; - createContainerLifetime(): ILifetime; + containerSlot(slotId: string): ILifetimeSlot; } export interface IActivationContext extends ILifetimeContext, ServiceLocator { register(name: K, service: DescriptorMap[K]): void; + + fail(error: unknown): never; } /** @@ -223,9 +225,6 @@ export interface ServiceLocator { resolve(name: K, def: T): NonNullable | T; } -export interface LifetimeContainer { - createLifetime(): ILifetime; -} export interface IContainerBuilder { createServiceBuilder(name: K): diff --git a/src/main/ts/traits.ts b/src/main/ts/traits.ts --- a/src/main/ts/traits.ts +++ b/src/main/ts/traits.ts @@ -46,4 +46,7 @@ export const oid = (in const val = (instance as OidSlot)[_oid]; return val ? val : ((instance as OidSlot)[_oid] = `oid_${++_nextOid}`); -}; \ No newline at end of file +}; + +export const prototype = (instance: T | null): T => + Object.create(instance) as T; \ No newline at end of file diff --git a/src/tsconfig.json b/src/tsconfig.json --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -6,7 +6,7 @@ "listFiles": true, "strict": true, "types": [], - "target": "ES5", - "lib": ["es5", "es2015.promise", "es2015.symbol", "es2015.iterable", "dom", "scripthost"] + "target": "ES2018", + "lib": ["ES2018", "DOM", "ScriptHost"] } } \ No newline at end of file