LifetimeManager.ts
216 lines
| 6.0 KiB
| video/mp2t
|
TypeScriptLexer
|
|
r0 | import { IDestroyable, MapOf } from "../interfaces"; | ||
| import { argumentNotNull, isDestroyable, argumentNotEmptyString, isRemovable } from "../safe"; | ||||
| import { ILifetime, ServiceContainer } from "./interfaces"; | ||||
| import { ActivationContext } from "./ActivationContext"; | ||||
| function safeCall(item: () => void) { | ||||
| try { | ||||
| item(); | ||||
| } catch { | ||||
| // silence! | ||||
| } | ||||
| } | ||||
| const emptyLifetime: ILifetime = Object.freeze({ | ||||
| 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]`; | ||||
| } | ||||
| }); | ||||
| const unknownLifetime: ILifetime = Object.freeze({ | ||||
| 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"); | ||||
| }, | ||||
| toString() { | ||||
| return `[object UnknownLifetime]`; | ||||
| } | ||||
| }); | ||||
| let nextId = 0; | ||||
| const singletons: any = {}; | ||||
| export class LifetimeManager implements IDestroyable { | ||||
| private _cleanup: (() => void)[] = []; | ||||
| private _cache: MapOf<any> = {}; | ||||
| private _destroyed = false; | ||||
| private _pending: MapOf<boolean> = {}; | ||||
| create(): ILifetime { | ||||
| const self = this; | ||||
| const id = ++nextId; | ||||
| return { | ||||
| has() { | ||||
| return (id in self._cache); | ||||
| }, | ||||
| get() { | ||||
| const t = self._cache[id]; | ||||
| if (t === undefined) | ||||
| throw new Error(`The item with with the key ${id} isn't found`); | ||||
| return t; | ||||
| }, | ||||
| initialize() { | ||||
| if (self._pending[id]) | ||||
| throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`); | ||||
| self._pending[id] = true; | ||||
| }, | ||||
| store(item: any, cleanup?: (item: any) => void) { | ||||
| argumentNotNull(id, "id"); | ||||
| argumentNotNull(item, "item"); | ||||
| if (this.has()) | ||||
| throw new Error(`The item with with the key ${id} already registered with this lifetime manager`); | ||||
| delete self._pending[id]; | ||||
| self._cache[id] = item; | ||||
| if (self._destroyed) | ||||
| throw new Error("Lifetime manager is destroyed"); | ||||
| if (cleanup) { | ||||
| self._cleanup.push(() => cleanup(item)); | ||||
| } else if (isDestroyable(item)) { | ||||
| self._cleanup.push(() => item.destroy()); | ||||
| } | ||||
| } | ||||
| }; | ||||
| } | ||||
| destroy() { | ||||
| if (!this._destroyed) { | ||||
| this._destroyed = true; | ||||
| this._cleanup.forEach(safeCall); | ||||
| this._cleanup.length = 0; | ||||
| } | ||||
| } | ||||
| static empty(): ILifetime { | ||||
| return emptyLifetime; | ||||
| } | ||||
| static hierarchyLifetime() { | ||||
| let _lifetime = unknownLifetime; | ||||
| return { | ||||
| initialize(context: ActivationContext<any>) { | ||||
| if (_lifetime !== unknownLifetime) | ||||
| throw new Error("Cyclic reference activation detected"); | ||||
| _lifetime = context.getContainer().getLifetimeManager().create(); | ||||
| }, | ||||
| get() { | ||||
| return _lifetime.get(); | ||||
| }, | ||||
| has() { | ||||
| return _lifetime.has(); | ||||
| }, | ||||
| store(item: any, cleanup?: (item: any) => void) { | ||||
| return _lifetime.store(item, cleanup); | ||||
| }, | ||||
| toString() { | ||||
| return `[object HierarchyLifetime, has=${this.has()}]`; | ||||
| } | ||||
| }; | ||||
| } | ||||
| static contextLifetime() { | ||||
| let _lifetime = unknownLifetime; | ||||
| return { | ||||
| initialize(context: ActivationContext<any>) { | ||||
| if (_lifetime !== unknownLifetime) | ||||
| throw new Error("Cyclic reference detected"); | ||||
| _lifetime = context.createLifetime(); | ||||
| }, | ||||
| get() { | ||||
| return _lifetime.get(); | ||||
| }, | ||||
| has() { | ||||
| return _lifetime.has(); | ||||
| }, | ||||
| store(item: any) { | ||||
| _lifetime.store(item); | ||||
| }, | ||||
| toString() { | ||||
| return `[object ContextLifetime, has=${this.has()}]`; | ||||
| } | ||||
| }; | ||||
| } | ||||
| static singletonLifetime(typeId: string) { | ||||
| argumentNotEmptyString(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]; | ||||
| }, | ||||
| initialize() { | ||||
| if (pending) | ||||
| throw new Error("Cyclic reference detected"); | ||||
| pending = true; | ||||
| }, | ||||
| store(item: any) { | ||||
| singletons[typeId] = item; | ||||
| pending = false; | ||||
| }, | ||||
| toString() { | ||||
| return `[object SingletonLifetime, has=${this.has()}, typeId=${typeId}]`; | ||||
| } | ||||
| }; | ||||
| } | ||||
| static containerLifetime(container: ServiceContainer<any>) { | ||||
| let _lifetime = unknownLifetime; | ||||
| return { | ||||
| initialize(context: ActivationContext<any>) { | ||||
| if (_lifetime !== unknownLifetime) | ||||
| throw new Error("Cyclic reference detected"); | ||||
| _lifetime = container.getLifetimeManager().create(); | ||||
| }, | ||||
| get() { | ||||
| return _lifetime.get(); | ||||
| }, | ||||
| has() { | ||||
| return _lifetime.has(); | ||||
| }, | ||||
| store(item: any) { | ||||
| _lifetime.store(item); | ||||
| }, | ||||
| toString() { | ||||
| return `[object ContainerLifetime, has=${_lifetime.has()}]`; | ||||
| } | ||||
| }; | ||||
| } | ||||
| } | ||||
