|
|
import { IActivationContext, ILifetime, ILifetimeManager } from "./interfaces";
|
|
|
import { ActivationContext } from "./ActivationContext";
|
|
|
import { argumentNotNull, isDestroyable } from "./traits";
|
|
|
|
|
|
const safeCall = (item: () => void) => {
|
|
|
try {
|
|
|
item();
|
|
|
} catch {
|
|
|
// silence!
|
|
|
}
|
|
|
};
|
|
|
|
|
|
const _emptyLifetime = Object.freeze({
|
|
|
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]`;
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
const _unknownLifetime = Object.freeze({
|
|
|
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;
|
|
|
|
|
|
const singletons: { [K: string]: unknown } = {};
|
|
|
|
|
|
export class LifetimeManager implements ILifetimeManager {
|
|
|
private _cleanup: (() => void)[] = [];
|
|
|
private readonly _cache: { [K: string]: unknown } = {};
|
|
|
private _destroyed = false;
|
|
|
|
|
|
private readonly _pending: { [K: string]: unknown } = {};
|
|
|
|
|
|
create<T>(): ILifetime<T> & { remove(): void; } {
|
|
|
const id = ++nextId;
|
|
|
return {
|
|
|
has: () => id in this._cache,
|
|
|
|
|
|
get: () => {
|
|
|
const t = this._cache[id] as T;
|
|
|
if (t === undefined || t === null)
|
|
|
throw new Error(`The item with with the key ${id} isn't found`);
|
|
|
return t;
|
|
|
},
|
|
|
|
|
|
initialize: () => {
|
|
|
if (this._pending[id])
|
|
|
throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
|
|
|
this._pending[id] = true;
|
|
|
},
|
|
|
|
|
|
store: (item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void) => {
|
|
|
if (id in this._cache)
|
|
|
throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
|
|
|
if (this._destroyed)
|
|
|
throw new Error("Lifetime manager is destroyed");
|
|
|
|
|
|
delete this._pending[id];
|
|
|
|
|
|
this._cache[id] = item;
|
|
|
|
|
|
if (cleanup) {
|
|
|
this._cleanup.push(() => cleanup(item));
|
|
|
} else if (isDestroyable(item)) {
|
|
|
this._cleanup.push(() => item.destroy());
|
|
|
}
|
|
|
},
|
|
|
|
|
|
remove: () => {
|
|
|
if (this._pending[id])
|
|
|
throw new Error(`The item '${id}' can't be removed before it has been stored`);
|
|
|
delete this._cache[id];
|
|
|
}
|
|
|
};
|
|
|
}
|
|
|
|
|
|
destroy() {
|
|
|
if (!this._destroyed) {
|
|
|
this._destroyed = true;
|
|
|
this._cleanup.forEach(safeCall);
|
|
|
this._cleanup.length = 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
export const emptyLifetime = <T>(): ILifetime<T> => {
|
|
|
return _emptyLifetime;
|
|
|
};
|
|
|
|
|
|
export const hierarchyLifetime = <T>(): ILifetime<T> => {
|
|
|
let _lifetime: ILifetime<T> = _unknownLifetime;
|
|
|
return {
|
|
|
initialize(context: IActivationContext<object>) {
|
|
|
if (_lifetime !== _unknownLifetime)
|
|
|
throw new Error("Cyclic reference activation detected");
|
|
|
|
|
|
_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())}]`;
|
|
|
}
|
|
|
};
|
|
|
};
|
|
|
|
|
|
export const contextLifetime = <T>(): ILifetime<T> => {
|
|
|
let _lifetime: ILifetime<T> = _unknownLifetime;
|
|
|
return {
|
|
|
initialize(context: ActivationContext<object>) {
|
|
|
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())}]`;
|
|
|
}
|
|
|
};
|
|
|
};
|
|
|
|
|
|
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}]`;
|
|
|
}
|
|
|
};
|
|
|
};
|
|
|
|
|
|
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();
|
|
|
},
|
|
|
store(item: NonNullable<T>) {
|
|
|
_lifetime.store(item);
|
|
|
},
|
|
|
toString() {
|
|
|
return `[object ContainerLifetime, has=${String(_lifetime.has())}]`;
|
|
|
}
|
|
|
};
|
|
|
};
|
|
|
|