LifetimeManager.ts
211 lines
| 6.0 KiB
| video/mp2t
|
TypeScriptLexer
|
|
r5 | import { IActivationContext, IDestroyable, ILifetime } 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 | |||
|
|
r1 | 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]`; | ||||
| } | ||||
| }); | ||||
|
|
r1 | 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"); | ||||
| }, | ||||
| toString() { | ||||
| return `[object UnknownLifetime]`; | ||||
| } | ||||
| }); | ||||
| let nextId = 0; | ||||
|
|
r1 | const singletons: { [K:string]: unknown} = {}; | ||
|
|
r0 | |||
| export class LifetimeManager implements IDestroyable { | ||||
| private _cleanup: (() => void)[] = []; | ||||
|
|
r1 | private readonly _cache: {[K: string]: unknown} = {}; | ||
|
|
r0 | private _destroyed = false; | ||
|
|
r1 | private readonly _pending: {[K: string]: unknown} = {}; | ||
|
|
r0 | |||
|
|
r1 | create<T>(): ILifetime<T> { | ||
|
|
r0 | const id = ++nextId; | ||
| return { | ||||
|
|
r1 | has: () => id in this._cache, | ||
| get: () => { | ||||
| const t = this._cache[id]; | ||||
| if (t === undefined) | ||||
| throw new Error(`The item with with the key ${id} isn't found`); | ||||
| return t as 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 | }, | ||
|
|
r1 | store: (item: T, cleanup?: (item: T) => void) => { | ||
|
|
r0 | argumentNotNull(id, "id"); | ||
| argumentNotNull(item, "item"); | ||||
|
|
r1 | if (id in this._cache) | ||
|
|
r0 | throw new Error(`The item with with the key ${id} already registered with this lifetime manager`); | ||
|
|
r1 | delete this._pending[id]; | ||
|
|
r0 | |||
|
|
r1 | this._cache[id] = item; | ||
|
|
r0 | |||
|
|
r1 | if (this._destroyed) | ||
|
|
r0 | throw new Error("Lifetime manager is destroyed"); | ||
| if (cleanup) { | ||||
|
|
r1 | this._cleanup.push(() => cleanup(item)); | ||
|
|
r0 | } else if (isDestroyable(item)) { | ||
|
|
r1 | this._cleanup.push(() => item.destroy()); | ||
|
|
r0 | } | ||
| } | ||||
| }; | ||||
| } | ||||
| destroy() { | ||||
| if (!this._destroyed) { | ||||
| this._destroyed = true; | ||||
| this._cleanup.forEach(safeCall); | ||||
| this._cleanup.length = 0; | ||||
| } | ||||
| } | ||||
|
|
r1 | static empty<T>(): ILifetime<T> { | ||
|
|
r0 | return emptyLifetime; | ||
| } | ||||
|
|
r1 | static hierarchyLifetime<T>() { | ||
| let _lifetime: ILifetime<T> = unknownLifetime; | ||||
|
|
r0 | return { | ||
|
|
r5 | initialize(context: IActivationContext<object>) { | ||
|
|
r0 | if (_lifetime !== unknownLifetime) | ||
| throw new Error("Cyclic reference activation detected"); | ||||
|
|
r1 | _lifetime = context.createContainerLifetime<T>(); | ||
|
|
r0 | }, | ||
| get() { | ||||
| return _lifetime.get(); | ||||
| }, | ||||
| has() { | ||||
| return _lifetime.has(); | ||||
| }, | ||||
|
|
r1 | store(item: T, cleanup?: (item: T) => void) { | ||
|
|
r0 | return _lifetime.store(item, cleanup); | ||
| }, | ||||
| toString() { | ||||
|
|
r1 | return `[object HierarchyLifetime, has=${String(this.has())}]`; | ||
|
|
r0 | } | ||
| }; | ||||
| } | ||||
|
|
r1 | static contextLifetime<T>() { | ||
| let _lifetime: ILifetime<T> = unknownLifetime; | ||||
|
|
r0 | return { | ||
|
|
r1 | initialize(context: ActivationContext<object>) { | ||
|
|
r0 | if (_lifetime !== unknownLifetime) | ||
| throw new Error("Cyclic reference detected"); | ||||
| _lifetime = context.createLifetime(); | ||||
| }, | ||||
| get() { | ||||
| return _lifetime.get(); | ||||
| }, | ||||
| has() { | ||||
| return _lifetime.has(); | ||||
| }, | ||||
|
|
r1 | store(item: T) { | ||
|
|
r0 | _lifetime.store(item); | ||
| }, | ||||
| toString() { | ||||
|
|
r1 | return `[object ContextLifetime, has=${String(this.has())}]`; | ||
|
|
r0 | } | ||
| }; | ||||
| } | ||||
|
|
r1 | static singletonLifetime<T>(typeId: string) { | ||
| argumentNotNull(typeId, "typeId"); | ||||
|
|
r0 | let pending = false; | ||
| return { | ||||
| has() { | ||||
| return typeId in singletons; | ||||
| }, | ||||
| get() { | ||||
| if (!this.has()) | ||||
| throw new Error(`The instance ${typeId} doesn't exists`); | ||||
|
|
r1 | return singletons[typeId] as T; | ||
|
|
r0 | }, | ||
| initialize() { | ||||
| if (pending) | ||||
| throw new Error("Cyclic reference detected"); | ||||
| pending = true; | ||||
| }, | ||||
|
|
r1 | store(item: T) { | ||
|
|
r0 | singletons[typeId] = item; | ||
| pending = false; | ||||
| }, | ||||
| toString() { | ||||
|
|
r1 | return `[object SingletonLifetime, has=${String(this.has())}, typeId=${typeId}]`; | ||
|
|
r0 | } | ||
| }; | ||||
| } | ||||
|
|
r1 | static containerLifetime<T>(container: { createLifetime<X>(): ILifetime<X>}) { | ||
| let _lifetime: ILifetime<T> = unknownLifetime; | ||||
|
|
r0 | return { | ||
|
|
r1 | initialize() { | ||
|
|
r0 | if (_lifetime !== unknownLifetime) | ||
| throw new Error("Cyclic reference detected"); | ||||
|
|
r1 | _lifetime = container.createLifetime(); | ||
|
|
r0 | }, | ||
| get() { | ||||
| return _lifetime.get(); | ||||
| }, | ||||
| has() { | ||||
| return _lifetime.has(); | ||||
| }, | ||||
|
|
r1 | store(item: T) { | ||
|
|
r0 | _lifetime.store(item); | ||
| }, | ||||
| toString() { | ||||
|
|
r1 | return `[object ContainerLifetime, has=${String(_lifetime.has())}]`; | ||
|
|
r0 | } | ||
| }; | ||||
| } | ||||
| } | ||||
