LifetimeManager.ts
243 lines
| 6.8 KiB
| video/mp2t
|
TypeScriptLexer
|
|
r10 | import { IActivationContext, ILifetime, ILifetimeContext, ILifetimeManager, ILifetimeSlot } 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 | |||
|
|
r10 | const _emptySlot = Object.freeze({ | ||
| has: () => false, | ||||
|
|
r0 | |||
|
|
r10 | initialize: () => void (0), | ||
|
|
r0 | |||
|
|
r10 | get: () => { | ||
|
|
r0 | throw new Error("The specified item isn't registered with this lifetime manager"); | ||
| }, | ||||
|
|
r10 | store: () => void (0) | ||
|
|
r0 | }); | ||
|
|
r10 | const _unknonwSlot = Object.freeze({ | ||
| has: () => false, | ||||
| initialize: () => { | ||||
|
|
r0 | throw new Error("Can't call initialize on the unknown lifetime object"); | ||
| }, | ||||
|
|
r10 | get: () => { | ||
|
|
r0 | throw new Error("The lifetime object isn't initialized"); | ||
| }, | ||||
|
|
r10 | store: () => { | ||
|
|
r0 | throw new Error("Can't store a value in the unknown lifetime object"); | ||
| } | ||||
| }); | ||||
| let nextId = 0; | ||||
|
|
r9 | const singletons: { [K: string]: unknown } = {}; | ||
|
|
r0 | |||
|
|
r10 | |||
| const pendingSlot = <T>(store: (item: T) => void) => ({ | ||||
| has: () => false, | ||||
| get: () => { | ||||
| throw new Error("The value in this slot doesn't exist"); | ||||
| }, | ||||
| initialize: () => { | ||||
| throw new Error("Cyclic reference detected"); | ||||
| }, | ||||
| store | ||||
| }); | ||||
| const noop = () => void(0); | ||||
| const valueSlot = <T>(value: T, cleanup: (item: T) => void) => ({ | ||||
| has: () => true, | ||||
| get: () => value, | ||||
| initialize: () => { | ||||
| throw new Error("The slot already has a value"); | ||||
| }, | ||||
| store: () => { | ||||
| throw new Error("The slot already has a value"); | ||||
| } | ||||
| }); | ||||
|
|
r9 | export class LifetimeManager implements ILifetimeManager { | ||
|
|
r0 | private _destroyed = false; | ||
|
|
r10 | private readonly _slots: Record<string, ILifetimeSlot<unknown>> = {}; | ||
|
|
r1 | |||
|
|
r10 | slot<T>(cookie: string): ILifetimeSlot<T> { | ||
| if (cookie in this._slots) | ||||
| return this._slots[cookie] as ILifetimeSlot<T>; | ||||
|
|
r0 | |||
|
|
r10 | const store = (item: T, cleanup?: (item: T) => void) => { | ||
| this._assertNotDestroyed(); | ||||
| this._slots[cookie] = valueSlot( | ||||
| item, | ||||
| cleanup ?? isDestroyable(item) ? () => item.destroy() : noop | ||||
| ); | ||||
| }; | ||||
|
|
r0 | |||
|
|
r10 | return { | ||
| has: () => false, | ||||
| get: () => { | ||||
| throw new Error("The value isn't stored in this slot"); | ||||
|
|
r9 | }, | ||
|
|
r10 | store, | ||
| initialize: () => { | ||||
| this._assertNotDestroyed(); | ||||
| this._slots[cookie] = pendingSlot(store); | ||||
|
|
r0 | } | ||
| }; | ||||
| } | ||||
|
|
r10 | private _assertNotDestroyed() { | ||
| if (this._destroyed) | ||||
| throw new Error("The lifetime manager is destroyed"); | ||||
| } | ||||
|
|
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 { | ||||
|
|
r10 | initialize(context: ILifetimeContext) { | ||
|
|
r9 | 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 | |||
|
|
r10 | /** | ||
| * Creates a lifetime instance bound to the current activation context. This | ||||
| * lifetime will store the service instance per activation context. Every | ||||
| * top level service resolution will create a new activation context. This | ||||
| * context is propagated to subsequent service resolution thus all services | ||||
| * with context lifetime will be shared among their consumers. | ||||
| * | ||||
| * @returns The instance of the lifetime. | ||||
| */ | ||||
|
|
r9 | export const contextLifetime = <T>(): ILifetime<T> => { | ||
| let _lifetime: ILifetime<T> = _unknownLifetime; | ||||
| return { | ||||
|
|
r10 | initialize(context: ILifetimeContext) { | ||
|
|
r9 | 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 | |||
|
|
r10 | /** | ||
| * Creates the lifetime for the service which will allow existence only one | ||||
| * instance with the specified {@linkcode typeId}. If there will be created | ||||
| * several lifetime instances with same `typeId` in the runtime, they will | ||||
| * share the same service instance. | ||||
| * | ||||
| * @param typeId The identified for the global instance, usually this is a | ||||
| * fully qualified class name | ||||
| * @returns The lifetime instance | ||||
| */ | ||||
|
|
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}]`; | ||||
| } | ||||
| }; | ||||
| }; | ||||
|
|
r10 | /** Creates a lifetime bound to the specified container. Using this lifetime | ||
| * will create a single service instance per the specified container. | ||||
| * | ||||
| * @param container The container which will manage the lifetime for the service | ||||
| */ | ||||
|
|
r9 | 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(); | ||||
| }, | ||||
|
|
r10 | store(item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void) { | ||
| _lifetime.store(item, cleanup); | ||||
|
|
r9 | }, | ||
| toString() { | ||||
| return `[object ContainerLifetime, has=${String(_lifetime.has())}]`; | ||||
| } | ||||
| }; | ||||
| }; | ||||
