LifetimeManager.ts
174 lines
| 4.7 KiB
| video/mp2t
|
TypeScriptLexer
|
|
r12 | import { IDestroyable, ILifetimeContext, ILifetimeManager, ILifetimeSlot } from "./interfaces"; | ||
|
|
r1 | import { argumentNotNull, isDestroyable } from "./traits"; | ||
|
|
r0 | |||
|
|
r1 | const safeCall = (item: () => void) => { | ||
|
|
r0 | try { | ||
| item(); | ||||
| } catch { | ||||
| // silence! | ||||
| } | ||||
|
|
r1 | }; | ||
|
|
r0 | |||
|
|
r12 | const noop = () => { }; | ||
|
|
r11 | |||
|
|
r12 | const fail = (message: string) => (): never => { | ||
|
|
r11 | throw new Error(message); | ||
| }; | ||||
|
|
r10 | const _emptySlot = Object.freeze({ | ||
| has: () => false, | ||||
|
|
r0 | |||
|
|
r11 | initialize: noop, | ||
| get: fail("The specified item isn't registered with a lifetime manager"), | ||||
|
|
r0 | |||
|
|
r11 | store: noop, | ||
|
|
r0 | |||
|
|
r12 | remove: noop, | ||
|
|
r10 | |||
|
|
r12 | cleanup: noop, | ||
|
|
r0 | }); | ||
|
|
r13 | const _destroy = (item: unknown) => () => isDestroyable(item) && item.destroy() ; | ||
|
|
r10 | |||
|
|
r12 | const _makeCleanup = <T>(value: T, cleanup?: (item: T) => void) => | ||
|
|
r13 | cleanup ? () => cleanup(value) : _destroy(value); | ||
|
|
r12 | |||
| const newSlot = <T>(put: (item: ILifetimeSlot<T>) => void, remove: () => void): ILifetimeSlot<T> => ({ | ||||
|
|
r10 | has: () => false, | ||
|
|
r12 | initialize: () => put(pendingSlot(put, remove)), | ||
| get: fail("The slot doesn't hold a value"), | ||||
|
|
r10 | |||
|
|
r12 | store: (value, cleanup) => put(valueSlot(value, cleanup, remove)), | ||
|
|
r10 | |||
|
|
r12 | remove: noop, | ||
| cleanup: noop, | ||||
|
|
r10 | }); | ||
|
|
r12 | const pendingSlot = <T>(put: (item: ILifetimeSlot<T>) => void, remove: () => void): ILifetimeSlot<T> => ({ | ||
| has: () => false, | ||||
| get: fail("The value in this slot doesn't exist"), | ||||
| initialize: fail("Cyclic reference detected"), | ||||
| store: (value, cleanup) => put(valueSlot(value, cleanup, remove)), | ||||
| remove, | ||||
| cleanup: noop | ||||
| }); | ||||
| const valueSlot = <T>(value: T, cleanup: ((item: T) => void) | undefined, remove: () => void) => ({ | ||||
|
|
r10 | has: () => true, | ||
| get: () => value, | ||||
|
|
r12 | initialize: fail("The slot already has a value"), | ||
| store: fail("The slot already has a value"), | ||||
|
|
r10 | |||
|
|
r12 | cleanup: _makeCleanup(value, cleanup), | ||
| remove: remove | ||||
|
|
r10 | }); | ||
|
|
r9 | export class LifetimeManager implements ILifetimeManager { | ||
|
|
r0 | private _destroyed = false; | ||
|
|
r10 | private readonly _slots: Record<string, ILifetimeSlot<unknown>> = {}; | ||
|
|
r1 | |||
|
|
r12 | slot<T>(cookie: string | number): ILifetimeSlot<T> { | ||
|
|
r10 | if (cookie in this._slots) | ||
| return this._slots[cookie] as ILifetimeSlot<T>; | ||||
|
|
r0 | |||
|
|
r12 | return newSlot<T>(this._put(cookie), this._remove(cookie)); | ||
|
|
r0 | } | ||
|
|
r12 | private readonly _put = (id: string | number) => <T>(slot: ILifetimeSlot<T>) => { | ||
| this._assertNotDestroyed(); | ||||
| this._slots[id] = slot as ILifetimeSlot<unknown>; | ||||
| }; | ||||
| private readonly _remove = (id: string | number) => () => { | ||||
| this._assertNotDestroyed(); | ||||
| delete this._slots[id]; | ||||
| }; | ||||
|
|
r11 | |||
|
|
r10 | private _assertNotDestroyed() { | ||
| if (this._destroyed) | ||||
| throw new Error("The lifetime manager is destroyed"); | ||||
| } | ||||
|
|
r0 | destroy() { | ||
| if (!this._destroyed) { | ||||
| this._destroyed = true; | ||||
|
|
r12 | Object.values(this._slots).forEach(({ cleanup }) => safeCall(cleanup)); | ||
|
|
r0 | } | ||
| } | ||||
|
|
r9 | } | ||
|
|
r12 | export const emptyLifetime = <T>() => () => _emptySlot as ILifetimeSlot<T>; | ||
|
|
r0 | |||
|
|
r12 | let nextId = 1; | ||
|
|
r0 | |||
|
|
r13 | export const containerLifetime = <T>() => { | ||
| const slotId = nextId ++; | ||||
| return (context: ILifetimeContext) => | ||||
| context.ownerSlot<T>(slotId); | ||||
| }; | ||||
|
|
r12 | export const hierarchyLifetime = <T>() => { | ||
| const slotId = nextId++; | ||||
| return (context: ILifetimeContext) => | ||||
| context.containerSlot<T>(slotId); | ||||
|
|
r9 | }; | ||
|
|
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. | ||||
| */ | ||||
|
|
r12 | export const contextLifetime = <T>() => { | ||
| const slotId = nextId++; | ||||
| return (context: ILifetimeContext) => | ||||
| context.contextSlot<T>(slotId); | ||||
|
|
r9 | }; | ||
|
|
r0 | |||
|
|
r12 | const singletons = new LifetimeManager(); | ||
|
|
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 | ||||
| */ | ||||
|
|
r12 | export const singletonLifetime = <T>(typeId: string) => { | ||
|
|
r9 | argumentNotNull(typeId, "typeId"); | ||
|
|
r12 | |||
| return () => singletons.slot<T>(typeId); | ||||
|
|
r9 | }; | ||
|
|
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 | ||||
| */ | ||||
|
|
r12 | export const containerLifetime = <T>(manager: ILifetimeManager) => { | ||
| const slotId = nextId++; | ||||
| return () => manager.slot<T>(slotId); | ||||
|
|
r9 | }; | ||
