|
|
import { ILifetime, ILifetimeContext, ILifetimeManager, ILifetimeSlot } from "../typings/interfaces";
|
|
|
import { LifetimeSlot } from "./LifetimeSlot";
|
|
|
import { argumentNotNull } from "./traits";
|
|
|
|
|
|
const noop = () => { };
|
|
|
|
|
|
const fail = (message: string) => (): never => {
|
|
|
throw new Error(message);
|
|
|
};
|
|
|
|
|
|
const _emptySlot = Object.freeze({
|
|
|
has: () => false,
|
|
|
|
|
|
initialize: () => false,
|
|
|
|
|
|
get: fail("The specified item isn't registered with a lifetime manager"),
|
|
|
|
|
|
store: noop,
|
|
|
|
|
|
remove: noop,
|
|
|
|
|
|
cleanup: noop,
|
|
|
});
|
|
|
|
|
|
export class LifetimeManager implements ILifetimeManager {
|
|
|
private _destroyed = false;
|
|
|
|
|
|
private readonly _slots: Record<string, ILifetimeSlot<unknown>> = {};
|
|
|
|
|
|
slot<T>(slotId: string | number): ILifetimeSlot<T> {
|
|
|
if (this._destroyed)
|
|
|
throw new Error("The lifetime manager is destroyed");
|
|
|
|
|
|
if (slotId in this._slots)
|
|
|
return this._slots[slotId] as ILifetimeSlot<T>;
|
|
|
|
|
|
return this._slots[slotId] = new LifetimeSlot<T>(() => delete this._slots[slotId]);
|
|
|
}
|
|
|
|
|
|
|
|
|
destroy() {
|
|
|
if (!this._destroyed) {
|
|
|
this._destroyed = true;
|
|
|
Object.values(this._slots).forEach(slot => {
|
|
|
try {
|
|
|
slot.cleanup();
|
|
|
} catch {
|
|
|
// ignore
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
export const emptySlot = <T>() => _emptySlot as ILifetimeSlot<T>;
|
|
|
|
|
|
export const emptyLifetime = <T>() => emptySlot as ILifetime<T>;
|
|
|
|
|
|
export const scopeLifetime = <T>(level: number, slotId: string | number) =>
|
|
|
(context: ILifetimeContext) =>
|
|
|
context.scopeSlot<T>(level, slotId);
|
|
|
|
|
|
export const hierarchyLifetime = <T>(slotId: string | number) =>
|
|
|
(context: ILifetimeContext) =>
|
|
|
context.hierarchySlot<T>(slotId);
|
|
|
|
|
|
/**
|
|
|
* 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.
|
|
|
*/
|
|
|
export const contextLifetime = <T>(slotId: string | number) =>
|
|
|
(context: ILifetimeContext) =>
|
|
|
context.contextSlot<T>(slotId);
|
|
|
|
|
|
const singletons = new LifetimeManager();
|
|
|
|
|
|
/**
|
|
|
* 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
|
|
|
*/
|
|
|
export const singletonLifetime = <T>(typeId: string) => {
|
|
|
argumentNotNull(typeId, "typeId");
|
|
|
|
|
|
return () => singletons.slot<T>(typeId);
|
|
|
};
|
|
|
|