@elsikora/nestjs-crud-automator
Version:
A library for automating the creation of CRUD operations in NestJS.
187 lines (184 loc) • 12 kB
JavaScript
import { ApiFunctionContextStorage } from '../function/context-storage.class.js';
import { apiSubscriberRegistry } from './registry.class.js';
import { CONTROLLER_API_DECORATOR_CONSTANT } from '../../../constant/decorator/api/controller.constant.js';
import { SERVICE_API_DECORATOR_CONSTANT } from '../../../constant/decorator/api/service.constant.js';
import '../../../enum/decorator/api/action.enum.js';
import '../../../enum/decorator/api/authentication-type.enum.js';
import '../../../enum/decorator/api/controller/get-list/query/filter/missing-behavior.enum.js';
import '../../../enum/decorator/api/controller/get-list/query/unlisted-fields.enum.js';
import '../../../enum/decorator/api/controller/relation-reference-shape.enum.js';
import '../../../enum/decorator/api/controller/request/target.enum.js';
import '../../../enum/decorator/api/controller/request/transformer-type.enum.js';
import '../../../enum/decorator/api/controller/response-target.enum.js';
import '../../../enum/decorator/api/dto-type.enum.js';
import '../../../enum/decorator/api/function/context-storage-kind.enum.js';
import { EApiFunctionSubscriberTransactionExpectation } from '../../../enum/decorator/api/function/subscriber-transaction-expectation.enum.js';
import '../../../enum/decorator/api/function/transaction/event-status.enum.js';
import '../../../enum/decorator/api/function/transaction/failure-stage.enum.js';
import '../../../enum/decorator/api/function/transaction/mode.enum.js';
import '../../../enum/decorator/api/function/transaction/outcome.enum.js';
import '../../../enum/decorator/api/function/transaction/owner-kind.enum.js';
import '../../../enum/decorator/api/function/transaction/trace-type.enum.js';
import { EApiFunctionType } from '../../../enum/decorator/api/function/type.enum.js';
import { EApiSubscriberOnType } from '../../../enum/decorator/api/on-type.enum.js';
import '../../../enum/decorator/api/property/data-type.enum.js';
import '../../../enum/decorator/api/property/date/identifier.enum.js';
import '../../../enum/decorator/api/property/date/type.enum.js';
import '../../../enum/decorator/api/property/desribe-type.enum.js';
import '../../../enum/decorator/api/property/number-type.enum.js';
import '../../../enum/decorator/api/property/string-type.enum.js';
import '../../../enum/decorator/api/route/subscriber-authorization-expectation.enum.js';
import '../../../enum/decorator/api/route/type.enum.js';
import { CamelCaseString } from '../../../utility/camel-case-string.utility.js';
import { ErrorException } from '../../../utility/error/exception.utility.js';
import { LoggerUtility } from '../../../utility/logger.utility.js';
const subscriberLogger = LoggerUtility.getLogger("ApiSubscriberExecutor");
class ApiSubscriberExecutor {
static async executeFunctionBeforeSubscribers(constructor, entity, functionType, context, action) {
try {
const result = await ApiSubscriberExecutor.executeFunctionSubscribers(constructor, entity, functionType, EApiSubscriberOnType.BEFORE, context, action);
ApiSubscriberExecutor.assertFunctionSubscribersTransactionExpectation(constructor, entity, functionType, EApiSubscriberOnType.AFTER, context, action);
return result;
}
catch (error) {
const errorExecutionContext = {
action: context.action,
DATA: context.DATA,
ENTITY: context.ENTITY,
FUNCTION_TYPE: context.FUNCTION_TYPE,
};
await ApiSubscriberExecutor.executeFunctionErrorSubscribers(constructor, entity, functionType, EApiSubscriberOnType.BEFORE_ERROR, errorExecutionContext, error, action ?? context.action);
throw error;
}
}
static async executeFunctionErrorSubscribers(constructor, entity, functionType, onType, context, error, action) {
if (!Reflect.hasMetadata(SERVICE_API_DECORATOR_CONSTANT.OBSERVABLE_METADATA_KEY, constructor)) {
return;
}
const entityName = ApiSubscriberExecutor.resolveEntityName(entity, context);
const subscribers = apiSubscriberRegistry.getFunctionSubscribers(entityName, functionType, action ?? context.action);
await ApiFunctionContextStorage.runWithoutStepContext(async () => {
for (const subscriber of subscribers) {
const hookName = ApiSubscriberExecutor.resolveHookName(onType, functionType);
const hook = Reflect.get(subscriber, hookName);
if (typeof hook === "function") {
const properties = apiSubscriberRegistry.getFunctionSubscriberProperties(subscriber);
ApiSubscriberExecutor.assertFunctionSubscriberTransactionExpectation(subscriber.constructor.name, properties, context);
subscriberLogger.verbose(`Executing function error hook ${hookName} from ${subscriber.constructor.name} for entity ${entityName}`);
await hook.call(subscriber, context, error);
}
}
});
}
static async executeFunctionSubscribers(constructor, entity, functionType, onType, context, action) {
if (!Reflect.hasMetadata(SERVICE_API_DECORATOR_CONSTANT.OBSERVABLE_METADATA_KEY, constructor)) {
return context.result;
}
const entityName = ApiSubscriberExecutor.resolveEntityName(entity, context);
const subscribers = apiSubscriberRegistry.getFunctionSubscribers(entityName, functionType, action ?? context.action);
let result = context.result;
await ApiFunctionContextStorage.runWithoutStepContext(async () => {
for (const subscriber of subscribers) {
const hookName = ApiSubscriberExecutor.resolveHookName(onType, functionType);
const hook = Reflect.get(subscriber, hookName);
if (typeof hook === "function") {
const properties = apiSubscriberRegistry.getFunctionSubscriberProperties(subscriber);
ApiSubscriberExecutor.assertFunctionSubscriberTransactionExpectation(subscriber.constructor.name, properties, context);
subscriberLogger.verbose(`Executing function hook ${hookName} from ${subscriber.constructor.name} for entity ${entityName}`);
const hookResult = (await hook.call(subscriber, { ...context, result }));
if (hookResult !== undefined) {
result = hookResult;
}
}
}
});
return result;
}
static async executeRouteErrorSubscribers(constructor, entity, routeType, onType, context, error, action) {
if (!Reflect.hasMetadata(CONTROLLER_API_DECORATOR_CONSTANT.OBSERVABLE_METADATA_KEY, constructor)) {
return;
}
const entityName = ApiSubscriberExecutor.resolveEntityName(entity, context);
const subscribers = apiSubscriberRegistry.getRouteSubscribers(entityName, constructor, routeType, action ?? context.action);
for (const subscriber of subscribers) {
const hookName = ApiSubscriberExecutor.resolveHookName(onType, routeType);
const hook = Reflect.get(subscriber, hookName);
if (typeof hook === "function") {
subscriberLogger.verbose(`Executing route error hook ${hookName} from ${subscriber.constructor.name} for entity ${entityName}`);
await hook.call(subscriber, context, error);
}
}
}
static async executeRouteSubscribers(constructor, entity, routeType, onType, context, action) {
if (!Reflect.hasMetadata(CONTROLLER_API_DECORATOR_CONSTANT.OBSERVABLE_METADATA_KEY, constructor)) {
return context.result;
}
const entityName = ApiSubscriberExecutor.resolveEntityName(entity, context);
const subscribers = apiSubscriberRegistry.getRouteSubscribers(entityName, constructor, routeType, action ?? context.action);
let result = context.result;
for (const subscriber of subscribers) {
const hookName = ApiSubscriberExecutor.resolveHookName(onType, routeType);
const hook = Reflect.get(subscriber, hookName);
if (typeof hook === "function") {
subscriberLogger.verbose(`Executing route hook ${hookName} from ${subscriber.constructor.name} for entity ${entityName}`);
const hookResult = (await hook.call(subscriber, { ...context, result }));
if (hookResult !== undefined) {
result = hookResult;
}
}
}
return result;
}
static assertFunctionSubscribersTransactionExpectation(constructor, entity, functionType, onType, context, action) {
if (!Reflect.hasMetadata(SERVICE_API_DECORATOR_CONSTANT.OBSERVABLE_METADATA_KEY, constructor)) {
return;
}
const entityName = ApiSubscriberExecutor.resolveEntityName(entity, context);
const subscribers = apiSubscriberRegistry.getFunctionSubscribers(entityName, functionType, action ?? context.action);
for (const subscriber of subscribers) {
if (!ApiSubscriberExecutor.hasFunctionSubscriberHook(subscriber, onType, functionType)) {
continue;
}
const properties = apiSubscriberRegistry.getFunctionSubscriberProperties(subscriber);
ApiSubscriberExecutor.assertFunctionSubscriberTransactionExpectation(subscriber.constructor.name, properties, context);
}
}
static assertFunctionSubscriberTransactionExpectation(subscriberName, properties, context) {
const transactionExpectation = properties?.transaction?.expectation;
if (transactionExpectation !== EApiFunctionSubscriberTransactionExpectation.MANDATORY && transactionExpectation !== EApiFunctionSubscriberTransactionExpectation.REQUIRED) {
return;
}
const eventManager = ApiSubscriberExecutor.resolveFunctionSubscriberEventManager(context);
if (eventManager === null || eventManager === undefined) {
throw ErrorException(`Function subscriber "${subscriberName}" declares transaction expectation "${transactionExpectation}" but no event manager was provided`);
}
}
static hasFunctionSubscriberHook(subscriber, onType, functionType) {
return typeof subscriber[ApiSubscriberExecutor.resolveHookName(onType, functionType)] === "function";
}
static resolveEntityName(entity, context) {
const data = context?.DATA;
const entityMetadataName = data && typeof data === "object" && "entityMetadata" in data ? data.entityMetadata?.name : undefined;
if (typeof entityMetadataName === "string" && entityMetadataName.length > 0) {
return entityMetadataName;
}
const propertyEntityName = data && typeof data === "object" && "properties" in data ? data.properties?.entity?.name : undefined;
if (typeof propertyEntityName === "string" && propertyEntityName.length > 0) {
return propertyEntityName;
}
const repositoryName = data && typeof data === "object" && "repository" in data ? (data.repository?.metadata?.name ?? data.repository?.target?.name) : undefined;
if (typeof repositoryName === "string" && repositoryName.length > 0) {
return repositoryName;
}
return entity.constructor.name;
}
static resolveFunctionSubscriberEventManager(context) {
const data = context.DATA;
return data && typeof data === "object" && "eventManager" in data ? data.eventManager : undefined;
}
static resolveHookName(onType, lifecycleType) {
return lifecycleType === undefined || lifecycleType === EApiFunctionType.CUSTOM ? `on${onType}Custom` : `on${onType}${CamelCaseString(lifecycleType)}`;
}
}
export { ApiSubscriberExecutor };
//# sourceMappingURL=executor.class.js.map