@elsikora/nestjs-crud-automator
Version:
A library for automating the creation of CRUD operations in NestJS.
227 lines (224 loc) • 11.6 kB
JavaScript
import { ApiFunctionTransactionCommitUnknownOutcomeException } from './exception/commit-unknown-outcome.class.js';
import { ApiFunctionTransactionPostCommitException } from './exception/post-commit.class.js';
import { ApiFunctionTransactionRollbackException } from './exception/rollback.class.js';
import { apiSubscriberRegistry } from '../../subscriber/registry.class.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 '../../../../enum/decorator/api/function/subscriber-transaction-expectation.enum.js';
import '../../../../enum/decorator/api/function/transaction/event-status.enum.js';
import { EApiFunctionTransactionFailureStage } from '../../../../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 { EApiFunctionTransactionTraceType } from '../../../../enum/decorator/api/function/transaction/trace-type.enum.js';
import '../../../../enum/decorator/api/function/type.enum.js';
import '../../../../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';
class ApiFunctionTransactionLifecycle {
static async executeAfterCommit(registry) {
const subscribers = ApiFunctionTransactionLifecycle.getMatchingSubscribers(registry);
const hookFailures = [];
for (const { context, subscriber } of subscribers) {
if (!subscriber.onAfterCommit) {
continue;
}
try {
await subscriber.onAfterCommit(context);
}
catch (error) {
hookFailures.push(ApiFunctionTransactionLifecycle.createFailure(EApiFunctionTransactionFailureStage.AFTER_COMMIT, error, subscriber));
}
}
if (hookFailures.length === 0) {
return;
}
const beforeErrorFailures = await ApiFunctionTransactionLifecycle.executeBeforeCommitErrorSubscribers(subscribers, hookFailures);
const transaction = registry.getTransaction();
const provisionalError = new ApiFunctionTransactionPostCommitException({
errorLifecycleFailures: beforeErrorFailures,
hookFailures,
owner: transaction.owner,
transactionId: transaction.id,
});
const afterErrorFailures = await ApiFunctionTransactionLifecycle.executeAfterCommitErrorSubscribers(subscribers, provisionalError);
throw new ApiFunctionTransactionPostCommitException({
errorLifecycleFailures: [...beforeErrorFailures, ...afterErrorFailures],
hookFailures,
owner: transaction.owner,
transactionId: transaction.id,
});
}
static async executeAfterRollback(registry, operationError, rollbackFailure) {
const subscribers = ApiFunctionTransactionLifecycle.getMatchingSubscribers(registry);
const hookFailures = [];
for (const { context, subscriber } of subscribers) {
if (!subscriber.onAfterRollback) {
continue;
}
try {
await subscriber.onAfterRollback(context);
}
catch (error) {
hookFailures.push(ApiFunctionTransactionLifecycle.createFailure(EApiFunctionTransactionFailureStage.AFTER_ROLLBACK, error, subscriber));
}
}
const rawFailures = rollbackFailure ? [rollbackFailure, ...hookFailures] : hookFailures;
if (rawFailures.length === 0) {
throw operationError;
}
const beforeErrorFailures = await ApiFunctionTransactionLifecycle.executeBeforeRollbackErrorSubscribers(subscribers, rawFailures);
const transaction = registry.getTransaction();
const provisionalError = new ApiFunctionTransactionRollbackException({
errorLifecycleFailures: beforeErrorFailures,
hookFailures,
operationError,
owner: transaction.owner,
rollbackFailure,
transactionId: transaction.id,
});
const afterErrorFailures = await ApiFunctionTransactionLifecycle.executeAfterRollbackErrorSubscribers(subscribers, provisionalError);
throw new ApiFunctionTransactionRollbackException({
errorLifecycleFailures: [...beforeErrorFailures, ...afterErrorFailures],
hookFailures,
operationError,
owner: transaction.owner,
rollbackFailure,
transactionId: transaction.id,
});
}
static async executeCommitUnknown(registry, error, cleanupFailure) {
const subscribers = ApiFunctionTransactionLifecycle.getMatchingSubscribers(registry);
const commitFailure = ApiFunctionTransactionLifecycle.createFailure(EApiFunctionTransactionFailureStage.COMMIT, error);
const rawFailures = cleanupFailure ? [commitFailure, cleanupFailure] : [commitFailure];
const beforeErrorFailures = await ApiFunctionTransactionLifecycle.executeBeforeCommitErrorSubscribers(subscribers, rawFailures);
const transaction = registry.getTransaction();
const provisionalError = new ApiFunctionTransactionCommitUnknownOutcomeException({
cleanupFailure,
commitFailure,
errorLifecycleFailures: beforeErrorFailures,
owner: transaction.owner,
transactionId: transaction.id,
});
const afterErrorFailures = await ApiFunctionTransactionLifecycle.executeAfterCommitErrorSubscribers(subscribers, provisionalError);
throw new ApiFunctionTransactionCommitUnknownOutcomeException({
cleanupFailure,
commitFailure,
errorLifecycleFailures: [...beforeErrorFailures, ...afterErrorFailures],
owner: transaction.owner,
transactionId: transaction.id,
});
}
static createFailure(stage, error, subscriber) {
return Object.freeze({
error,
stage,
subscriberName: subscriber?.constructor.name,
});
}
static async executeAfterCommitErrorSubscribers(subscribers, error) {
const failures = [];
for (const { context, subscriber } of subscribers) {
if (!subscriber.onAfterErrorCommit) {
continue;
}
try {
await subscriber.onAfterErrorCommit(context, error);
}
catch (lifecycleError) {
failures.push(ApiFunctionTransactionLifecycle.createFailure(EApiFunctionTransactionFailureStage.AFTER_ERROR_COMMIT, lifecycleError, subscriber));
}
}
return failures;
}
static async executeAfterRollbackErrorSubscribers(subscribers, error) {
const failures = [];
for (const { context, subscriber } of subscribers) {
if (!subscriber.onAfterErrorRollback) {
continue;
}
try {
await subscriber.onAfterErrorRollback(context, error);
}
catch (lifecycleError) {
failures.push(ApiFunctionTransactionLifecycle.createFailure(EApiFunctionTransactionFailureStage.AFTER_ERROR_ROLLBACK, lifecycleError, subscriber));
}
}
return failures;
}
static async executeBeforeCommitErrorSubscribers(subscribers, rawFailures) {
const failures = [];
const readonlyFailures = Object.freeze([...rawFailures]);
for (const { context, subscriber } of subscribers) {
if (!subscriber.onBeforeErrorCommit) {
continue;
}
try {
await subscriber.onBeforeErrorCommit(context, readonlyFailures);
}
catch (error) {
failures.push(ApiFunctionTransactionLifecycle.createFailure(EApiFunctionTransactionFailureStage.BEFORE_ERROR_COMMIT, error, subscriber));
}
}
return failures;
}
static async executeBeforeRollbackErrorSubscribers(subscribers, rawFailures) {
const failures = [];
const readonlyFailures = Object.freeze([...rawFailures]);
for (const { context, subscriber } of subscribers) {
if (!subscriber.onBeforeErrorRollback) {
continue;
}
try {
await subscriber.onBeforeErrorRollback(context, readonlyFailures);
}
catch (error) {
failures.push(ApiFunctionTransactionLifecycle.createFailure(EApiFunctionTransactionFailureStage.BEFORE_ERROR_ROLLBACK, error, subscriber));
}
}
return failures;
}
static getMatchingSubscribers(registry) {
const matchedEvents = new Map();
for (const event of registry.getObservableEvents()) {
if (event.functionType === EApiFunctionTransactionTraceType.STEP) {
continue;
}
const subscribers = apiSubscriberRegistry.getFunctionSubscribers(event.entityName, event.functionType, event.action);
for (const subscriber of subscribers) {
const events = matchedEvents.get(subscriber) ?? [];
events.push(event);
matchedEvents.set(subscriber, events);
}
}
return [...matchedEvents.entries()]
.toSorted((leftEntry, rightEntry) => {
const leftSubscriber = leftEntry[0];
const rightSubscriber = rightEntry[0];
const leftProperties = apiSubscriberRegistry.getFunctionSubscriberProperties(leftSubscriber);
const rightProperties = apiSubscriberRegistry.getFunctionSubscriberProperties(rightSubscriber);
const priorityDifference = (rightProperties?.priority ?? 0) - (leftProperties?.priority ?? 0);
return priorityDifference === 0 ? apiSubscriberRegistry.getFunctionSubscriberRegistrationOrder(leftSubscriber) - apiSubscriberRegistry.getFunctionSubscriberRegistrationOrder(rightSubscriber) : priorityDifference;
})
.map((entry) => ({
context: registry.createContext(entry[1]),
subscriber: entry[0],
}));
}
}
export { ApiFunctionTransactionLifecycle };
//# sourceMappingURL=lifecycle.class.js.map