LifetimeManager.ts
217 lines
| 6.0 KiB
| video/mp2t
|
TypeScriptLexer
|
|
r9 | import { IActivationContext, ILifetime, ILifetimeManager } from "./interfaces"; | ||
|
|
r0 | import { ActivationContext } from "./ActivationContext"; | ||
|
|
r1 | import { argumentNotNull, isDestroyable } from "./traits"; | ||
|
|
r0 | |||
|
|
r1 | const safeCall = (item: () => void) => { | ||
|
|
r0 | try { | ||
| item(); | ||||
| } catch { | ||||
| // silence! | ||||
| } | ||||
|
|
r1 | }; | ||
|
|
r0 | |||
|
|
r9 | const _emptyLifetime = Object.freeze({ | ||
|
|
r0 | has() { | ||
| return false; | ||||
| }, | ||||
| initialize() { | ||||
| }, | ||||
| get() { | ||||
| throw new Error("The specified item isn't registered with this lifetime manager"); | ||||
| }, | ||||
| store() { | ||||
| // does nothing | ||||
| }, | ||||
| toString() { | ||||
| return `[object EmptyLifetime]`; | ||||
| } | ||||
| }); | ||||
|
|
r9 | const _unknownLifetime = Object.freeze({ | ||
|
|
r0 | has() { | ||
| return false; | ||||
| }, | ||||
| 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"); | ||||
| }, | ||||
|
|
r9 | |||
|
|
r0 | toString() { | ||
| return `[object UnknownLifetime]`; | ||||
| } | ||||
| }); | ||||
| let nextId = 0; | ||||
|
|
r9 | const singletons: { [K: string]: unknown } = {}; | ||
|
|
r0 | |||
|
|
r9 | export class LifetimeManager implements ILifetimeManager { | ||
|
|
r0 | private _cleanup: (() => void)[] = []; | ||
|
|
r9 | private readonly _cache: { [K: string]: unknown } = {}; | ||
|
|
r0 | private _destroyed = false; | ||
|
|
r9 | private readonly _pending: { [K: string]: unknown } = {}; | ||
|
|
r0 | |||
|
|
r9 | create<T>(): ILifetime<T> & { remove(): void; } { | ||
|
|
r0 | const id = ++nextId; | ||
| return { | ||||
|
|
r1 | has: () => id in this._cache, | ||
| get: () => { | ||||
|
|
r9 | const t = this._cache[id] as T; | ||
| if (t === undefined || t === null) | ||||
|
|
r1 | throw new Error(`The item with with the key ${id} isn't found`); | ||
|
|
r9 | return t; | ||
|
|
r0 | }, | ||
|
|
r1 | initialize: () => { | ||
| if (this._pending[id]) | ||||
| throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`); | ||||
| this._pending[id] = true; | ||||
|
|
r0 | }, | ||
|
|
r9 | store: (item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void) => { | ||
|
|
r1 | if (id in this._cache) | ||
|
|
r0 | throw new Error(`The item with with the key ${id} already registered with this lifetime manager`); | ||
|
|
r9 | if (this._destroyed) | ||
| throw new Error("Lifetime manager is destroyed"); | ||||
|
|
r1 | delete this._pending[id]; | ||
|
|
r0 | |||
|
|
r1 | this._cache[id] = item; | ||
|
|
r0 | |||
| if (cleanup) { | ||||
|
|
r1 | this._cleanup.push(() => cleanup(item)); | ||
|
|
r0 | } else if (isDestroyable(item)) { | ||
|
|
r1 | this._cleanup.push(() => item.destroy()); | ||
|
|
r0 | } | ||
|
|
r9 | }, | ||
| remove: () => { | ||||
| if (this._pending[id]) | ||||
| throw new Error(`The item '${id}' can't be removed before it has been stored`); | ||||
| delete this._cache[id]; | ||||
|
|
r0 | } | ||
| }; | ||||
| } | ||||
| destroy() { | ||||
| if (!this._destroyed) { | ||||
| this._destroyed = true; | ||||
| this._cleanup.forEach(safeCall); | ||||
| this._cleanup.length = 0; | ||||
| } | ||||
| } | ||||
|
|
r9 | } | ||
| export const emptyLifetime = <T>(): ILifetime<T> => { | ||||
| return _emptyLifetime; | ||||
| }; | ||||
|
|
r0 | |||
|
|
r9 | export const hierarchyLifetime = <T>(): ILifetime<T> => { | ||
| let _lifetime: ILifetime<T> = _unknownLifetime; | ||||
| return { | ||||
| initialize(context: IActivationContext<object>) { | ||||
| if (_lifetime !== _unknownLifetime) | ||||
| throw new Error("Cyclic reference activation detected"); | ||||
|
|
r0 | |||
|
|
r9 | _lifetime = context.createContainerLifetime<T>(); | ||
| }, | ||||
| get() { | ||||
| return _lifetime.get(); | ||||
| }, | ||||
| has() { | ||||
| return _lifetime.has(); | ||||
| }, | ||||
| store(item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void) { | ||||
| return _lifetime.store(item, cleanup); | ||||
| }, | ||||
| toString() { | ||||
| return `[object HierarchyLifetime, has=${String(this.has())}]`; | ||||
| } | ||||
| }; | ||||
| }; | ||||
|
|
r0 | |||
|
|
r9 | export const contextLifetime = <T>(): ILifetime<T> => { | ||
| let _lifetime: ILifetime<T> = _unknownLifetime; | ||||
| return { | ||||
| initialize(context: ActivationContext<object>) { | ||||
| if (_lifetime !== _unknownLifetime) | ||||
| throw new Error("Cyclic reference detected"); | ||||
| _lifetime = context.createLifetime(); | ||||
| }, | ||||
| get() { | ||||
| return _lifetime.get(); | ||||
| }, | ||||
| has() { | ||||
| return _lifetime.has(); | ||||
| }, | ||||
| store(item: NonNullable<T>) { | ||||
| _lifetime.store(item); | ||||
| }, | ||||
| toString() { | ||||
| return `[object ContextLifetime, has=${String(this.has())}]`; | ||||
| } | ||||
| }; | ||||
| }; | ||||
|
|
r0 | |||
|
|
r9 | export const singletonLifetime = <T>(typeId: string): ILifetime<T> => { | ||
| 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<T>; | ||||
| }, | ||||
| initialize() { | ||||
| if (pending) | ||||
| throw new Error("Cyclic reference detected"); | ||||
| pending = true; | ||||
| }, | ||||
| store(item: NonNullable<T>) { | ||||
| singletons[typeId] = item; | ||||
| pending = false; | ||||
| }, | ||||
| toString() { | ||||
| return `[object SingletonLifetime, has=${String(this.has())}, typeId=${typeId}]`; | ||||
| } | ||||
| }; | ||||
| }; | ||||
| export const containerLifetime = <T>(container: { createLifetime<X>(): ILifetime<X> }) => { | ||||
| let _lifetime: ILifetime<T> = _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<T>) { | ||||
| _lifetime.store(item); | ||||
| }, | ||||
| toString() { | ||||
| return `[object ContainerLifetime, has=${String(_lifetime.has())}]`; | ||||
| } | ||||
| }; | ||||
| }; | ||||
