##// END OF EJS Templates
WIP lifetime services
WIP lifetime services

File last commit:

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