ts-ioc-container
Version:
Typescript IoC container
54 lines (53 loc) • 2.86 kB
JavaScript
import { createHookContext } from './HookContext';
import { isConstructor, promisify } from '../utils';
import { UnexpectedHookResultError } from '../errors/UnexpectedHookResultError';
import { toInjectFn } from '../injector/inject';
const isHookClassConstructor = (execute) => {
return isConstructor(execute) && execute.prototype.execute;
};
export const toHookFn = (execute) => isHookClassConstructor(execute) ? (context) => context.scope.resolveOne(execute).execute(context) : execute;
export const hook = (key, ...fns) => (target, propertyKey) => {
const hooks = Reflect.hasMetadata(key, target.constructor)
? Reflect.getMetadata(key, target.constructor)
: new Map();
hooks.set(propertyKey, (hooks.get(propertyKey) ?? []).concat(fns));
Reflect.defineMetadata(key, hooks, target.constructor);
};
export function getHooks(target, key) {
return Reflect.hasMetadata(key, target.constructor) ? Reflect.getMetadata(key, target.constructor) : new Map();
}
export function hasHooks(target, key) {
return Reflect.hasMetadata(key, target.constructor);
}
export const runHooks = (target, key, { scope, createContext = createHookContext, predicate = () => true, }) => {
const hooks = Array.from(getHooks(target, key).entries()).filter(([methodName]) => predicate(methodName));
const runMethodHooks = (methodName, executions) => {
const context = createContext(target, scope, methodName);
for (const execute of executions) {
const result = execute(context);
if (result instanceof Promise) {
throw new UnexpectedHookResultError(`Hook ${methodName} returned a promise, use runHooksAsync instead`);
}
}
};
for (const [methodName, executions] of hooks) {
runMethodHooks(methodName, executions.map(toHookFn));
}
};
export const runHooksAsync = (target, key, { scope, createContext = createHookContext, predicate = () => true, }) => {
const hooks = Array.from(getHooks(target, key).entries()).filter(([methodName]) => predicate(methodName));
const runMethodHooks = async (methodName, executions) => {
const context = createContext(target, scope, methodName);
for (const execute of executions) {
await promisify(execute(context));
}
};
return Promise.all(hooks.map(([methodName, executions]) => runMethodHooks(methodName, executions.map(toHookFn))));
};
export const injectProp = (fn) => (context) => context.setProperty(toInjectFn(fn));
export const onConstruct = (fn) => hook('onConstruct', fn);
export const runOnConstructHooks = (target, scope) => runHooks(target, 'onConstruct', { scope });
export const onDispose = hook('onDispose', (context) => {
context.invokeMethod({});
});
export const runOnDisposeHooks = (target, scope) => runHooks(target, 'onDispose', { scope });