@5minds/processcube_engine
Version:
The ProcessCube Engine. Stores and executes BPMNs.
908 lines • 63.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProcessInstance = void 0;
const dayjs_1 = __importDefault(require("dayjs"));
const inversify_1 = require("inversify");
const lodash_1 = __importDefault(require("lodash"));
const lodash_clonedeep_1 = __importDefault(require("lodash.clonedeep"));
const uuid = __importStar(require("uuid"));
const processcube_engine_sdk_1 = require("@5minds/processcube_engine_sdk");
const index_1 = require("../Contracts/InternalMessages/index");
const IocRegistrations_1 = require("../Contracts/IocRegistrations");
const DatabaseAdaptersSequelize_1 = require("../Tools/DatabaseAdaptersSequelize");
const EventAggregator_1 = __importDefault(require("../Tools/EventAggregator"));
const EventMiddlewareHandler_1 = require("../Tools/EventMiddlewareHandler");
const index_2 = require("../Tools/Iam/index");
const ProcessDefinitionMediator_1 = require("../Tools/ProcessDefinitionMediator");
const DataObjectFacade_1 = require("./Facades/DataObjectFacade");
const ProcessInstanceStore_1 = __importDefault(require("./Facades/ProcessInstanceStore"));
const FlowNodeHandlerFactory_1 = require("./FlowNodeHandler/FlowNodeHandlerFactory");
const index_3 = require("./index");
let ProcessInstance = class ProcessInstance {
container;
correlationDatabaseAdapter;
dataObjectFacade;
eventMiddlewareHandler;
flowNodeHandlerFactory;
flowNodeInstanceDatabaseAdapter;
iamService;
identityService;
processInstanceDatabaseAdapter;
processDefinitionMediator;
processTokenService;
processDefinitionId;
processDefinitionHash;
processModelId;
processModelName;
processModelVersion;
correlationId;
parentProcessInstance;
processInstanceId;
state;
owner;
initialTokenPayload = {};
startedByRootAccessToken = false;
embeddedProcessModelId;
correlationMetaData = {};
processInstanceMetaData = {};
processModelFacade;
logger;
ownerChangedSubscription;
correlationMetadataChangedSubscription;
startedAt;
finishedAt;
constructor(container, correlationDatabaseAdapter, dataObjectFacade, eventMiddlewareHandler, flowNodeHandlerFactory, flowNodeInstanceDatabaseAdapter, identityService, iamService, processDefinitionMediator, processInstanceDatabaseAdapter, processTokenService, parentProcessInstance, processInstanceId) {
this.container = container;
this.correlationDatabaseAdapter = correlationDatabaseAdapter;
this.dataObjectFacade = dataObjectFacade;
this.eventMiddlewareHandler = eventMiddlewareHandler;
this.flowNodeHandlerFactory = flowNodeHandlerFactory;
this.flowNodeInstanceDatabaseAdapter = flowNodeInstanceDatabaseAdapter;
this.identityService = identityService;
this.iamService = iamService;
this.processDefinitionMediator = processDefinitionMediator;
this.processInstanceDatabaseAdapter = processInstanceDatabaseAdapter;
this.processTokenService = processTokenService;
this.parentProcessInstance = parentProcessInstance;
this.processInstanceId = processInstanceId ?? uuid.v4();
}
getState() {
return this.state;
}
getProcessInstanceId() {
return this.processInstanceId;
}
getCorrelationId() {
return this.correlationId;
}
getDataObjectFacade() {
return this.dataObjectFacade;
}
getProcessDefinitionId() {
return this.processDefinitionId;
}
getProcessDefinitionHash() {
return this.processDefinitionHash;
}
getProcessModelId() {
return this.processModelId;
}
getEmbeddedProcessModelId() {
return this.embeddedProcessModelId;
}
getProcessModelName() {
return this.processModelName;
}
getProcessModelVersion() {
return this.processModelVersion;
}
getParentProcessInstance() {
return this.parentProcessInstance;
}
getProcessModelFacade() {
return this.processModelFacade;
}
getProcessTokenService() {
return this.processTokenService;
}
getOwner() {
return this.owner;
}
getInitialPayload() {
return lodash_1.default.cloneDeep(this.initialTokenPayload);
}
getCorrelationMetaData() {
return {
...this.correlationMetaData,
};
}
getProcessInstanceMetaData() {
return {
...this.processInstanceMetaData,
};
}
getStartedByRootAccessToken() {
return this.startedByRootAccessToken;
}
getStartedAt() {
return this.startedAt;
}
getFinishedAt() {
return this.finishedAt;
}
getContainer() {
return this.container;
}
isEmbeddedSubprocess() {
return this.embeddedProcessModelId !== undefined;
}
async execute(identity, options) {
await this.configure(identity, options);
await this.save(options);
this.startedAt = dayjs_1.default.utc().toDate();
const finalResult = await this.executeProcess(options);
const finalFlowNode = this.processModelFacade.getFlowNodeById(finalResult.flowNodeId);
return {
correlationId: this.correlationId,
processDefinitionId: this.processDefinitionId,
processModelId: this.processModelId,
embeddedProcessModelId: this.embeddedProcessModelId,
processInstanceId: this.correlationId,
parentProcessInstanceId: this.parentProcessInstance?.getProcessInstanceId(),
flowNodeId: finalResult.flowNodeId,
flowNodeName: finalFlowNode.name,
flowNodeInstanceId: finalResult.flowNodeInstanceId,
processInstanceOwner: this.getOwner(),
currentToken: finalResult.result,
eventType: finalFlowNode.eventType,
};
}
async resume(identity, processInstance, flowNodeInstances, parentDataObjectFacade, parentProcessModelFacade) {
if (processInstance.state === processcube_engine_sdk_1.ProcessInstanceState.error) {
// This is what would have happend, if the Process Instance had encountered an error during the resuming.
throw processInstance.error;
}
if (processInstance.state === processcube_engine_sdk_1.ProcessInstanceState.terminated) {
const errorToThrow = processInstance.error ?? new processcube_engine_sdk_1.BadRequestError('Process was terminated.');
errorToThrow.additionalInformation = {
processInstanceId: this.processInstanceId,
correlationId: this.correlationId,
processInstanceWasTerminated: true,
};
throw errorToThrow;
}
if (processInstance.state !== processcube_engine_sdk_1.ProcessInstanceState.running) {
this.logger.info('Process Instance was already finished. Nothing to do here.');
const endEventName = processInstance.state === processcube_engine_sdk_1.ProcessInstanceState.finished
? flowNodeInstances.find((flowNodeInstance) => flowNodeInstance.flowNodeId === processInstance.endEventId)?.flowNodeName
: undefined;
return {
correlationId: processInstance.correlationId,
processDefinitionId: processInstance.processDefinitionId,
processModelId: processInstance.processModelId,
embeddedProcessModelId: processInstance.embeddedProcessModelId,
processModelName: processInstance.processModelName,
processInstanceId: processInstance.processInstanceId,
parentProcessInstanceId: processInstance.parentProcessInstanceId,
flowNodeId: processInstance.endEventId,
flowNodeName: endEventName,
flowNodeInstanceId: processInstance.endEventId,
processInstanceOwner: identity,
currentToken: processInstance.endToken ?? {},
eventType: processInstance.endEventType,
};
}
const resumeOptions = {
processModelId: processInstance.processModelId,
processInstanceId: processInstance.processInstanceId,
startEventId: processInstance.startEventId,
correlationId: processInstance.correlationId,
initialToken: processInstance.startToken,
parentProcessInstanceId: processInstance.parentProcessInstanceId,
embeddedProcessModelId: processInstance.embeddedProcessModelId,
parentDataObjectFacade: parentDataObjectFacade,
parentProcessModelFacade: parentProcessModelFacade,
};
await this.configure(identity, resumeOptions, processInstance);
const flowNodeInstancesToResume = this.getFlowNodeInstancesToResume(flowNodeInstances);
const corruptedFlowNodeInstances = this.checkForFlowNodeInstancesWithMultipleSuccessors(flowNodeInstancesToResume);
if (corruptedFlowNodeInstances.length > 0) {
const msg = `Cannot resume Process Instance \`${processInstance.processInstanceId}\`, because it has Flow Node Instances with multiple successors, which is not allowed.`;
const error = new processcube_engine_sdk_1.BadRequestError(msg);
error.additionalInformation = {
processInstanceId: processInstance.processInstanceId,
corruptedFlowNodeInstances: corruptedFlowNodeInstances,
};
this.logger.error(msg, {
err: error,
});
await this.processInstanceDatabaseAdapter.finishWithError(processInstance.processInstanceId, error, undefined, true);
throw error;
}
this.logProcessResumed(resumeOptions.initialToken);
this.sendProcessResumedNotification();
let finalResult;
if (!flowNodeInstances || flowNodeInstances.length === 0) {
this.logger.info(`Resumable process instance ${this.processInstanceId} was not started. Executing instance normally.`, {
processModelId: this.processModelId,
processInstanceId: this.processInstanceId,
correlationId: this.correlationId,
parentProcessInstanceId: this.parentProcessInstance?.getProcessInstanceId(),
embeddedProcessModelId: this.embeddedProcessModelId,
});
finalResult = await this.executeProcess(resumeOptions);
}
else {
finalResult = await this.resumeProcess(flowNodeInstances, flowNodeInstancesToResume);
}
const finalFlowNode = this.processModelFacade.getFlowNodeById(finalResult.flowNodeId);
return {
correlationId: this.correlationId,
processDefinitionId: this.processDefinitionId,
processModelId: this.processModelId,
embeddedProcessModelId: this.embeddedProcessModelId,
processModelName: this.processModelName,
processInstanceId: this.processInstanceId,
flowNodeId: finalResult.flowNodeId,
flowNodeName: finalFlowNode.name,
flowNodeInstanceId: finalResult.flowNodeInstanceId,
processInstanceOwner: this.getOwner(),
currentToken: finalResult.result,
eventType: finalFlowNode.eventType,
};
}
kill(killerIdentity) {
const killEvent = index_1.eventAggregatorSettings.messagePaths.killProcessInstanceWithId.replace(index_1.eventAggregatorSettings.messageParams.processInstanceId, this.processInstanceId);
const eventPayload = {
killedBy: killerIdentity,
};
EventAggregator_1.default.publish(killEvent, eventPayload);
}
ensureOwnerHasClaim(claimName, claimValue) {
if (this.startedByRootAccessToken) {
return;
}
const ownerIsSuperAdmin = this.iamService.checkIfUserIsSuperAdmin(this.getOwner());
if (ownerIsSuperAdmin) {
return;
}
this.iamService.ensureHasClaim(this.getOwner(), claimName, claimValue);
}
async setCorrelationMetadataValue(key, rawValue, flowNode, flowNodeInstanceId, currentToken) {
try {
let parsedValue = await this.executeRuntimeExpression({
expression: `\`${rawValue}\``,
currentFlowNode: flowNode,
currentToken: currentToken,
previousFlowNode: this.getProcessModelFacade().getPreviousFlowNodesFor(flowNode)?.pop(),
allowNonObjectResults: true,
});
if (parsedValue === 'undefined' || parsedValue === 'null') {
parsedValue = null;
}
this.correlationMetaData[key] = parsedValue;
await this.correlationDatabaseAdapter.setMetadataValue(this.correlationId, key, parsedValue, flowNodeInstanceId);
return parsedValue;
}
catch (error) {
let errorMessage;
if (error.category) {
errorMessage = `The given Correlation metadata value could not be parsed. ${error.message.replace(`Failure in ${error.category}: `, '')}`;
}
else {
errorMessage = `The given Correlation metadata value could not be parsed. ${error.message}`;
}
const invalidMetadataValueError = new processcube_engine_sdk_1.BadRequestError(errorMessage, error.category);
invalidMetadataValueError.additionalInformation = {
correlationId: this.correlationId,
processInstanceId: this.processInstanceId,
flowNodeInstanceId: flowNodeInstanceId,
flowNodeId: flowNode.id,
flowNodeName: flowNode.name,
metaDataKey: key,
metaDataValue: rawValue,
};
throw invalidMetadataValueError;
}
}
async setProcessInstanceMetadataValue(key, rawValue, flowNode, flowNodeInstanceId, currentToken) {
try {
let parsedValue = await this.executeRuntimeExpression({
expression: `\`${rawValue}\``,
currentFlowNode: flowNode,
currentToken: currentToken,
previousFlowNode: this.getProcessModelFacade().getPreviousFlowNodesFor(flowNode)?.pop(),
allowNonObjectResults: true,
});
if (parsedValue === 'undefined' || parsedValue === 'null') {
parsedValue = null;
}
this.processInstanceMetaData[key] = parsedValue;
await this.processInstanceDatabaseAdapter.setMetadataValue(this.processInstanceId, key, parsedValue, flowNodeInstanceId);
return parsedValue;
}
catch (error) {
let errorMessage;
if (error.category) {
errorMessage = `The given ProcessInstance metadata value could not be parsed. ${error.message.replace(`Failure in ${error.category}: `, '')}`;
}
else {
errorMessage = `The given ProcessInstance metadata value could not be parsed. ${error.message}`;
}
const invalidMetadataValueError = new processcube_engine_sdk_1.BadRequestError(errorMessage, error.category);
invalidMetadataValueError.additionalInformation = {
processInstanceId: this.processInstanceId,
flowNodeId: flowNode.id,
flowNodeName: flowNode.name,
metaDataKey: key,
metaDataValue: rawValue,
};
throw invalidMetadataValueError;
}
}
async executeRuntimeExpression(config) {
const runtimeExpressionParameters = this.buildRuntimeExpressionDataForFlowNodeInstance(config);
return this.processTokenService.executeRuntimeExpression(config.expression, runtimeExpressionParameters, config.dataContract);
}
onProcessStarted(callback) {
const eventName = index_1.eventAggregatorSettings.messagePaths.processInstanceWithIdStarted.replace(index_1.eventAggregatorSettings.messageParams.processInstanceId, this.processInstanceId);
return EventAggregator_1.default.subscribeOnce(eventName, callback);
}
onProcessEnded(callback) {
const eventName = index_1.eventAggregatorSettings.messagePaths.processInstanceWithIdEnded.replace(index_1.eventAggregatorSettings.messageParams.processInstanceId, this.processInstanceId);
return EventAggregator_1.default.subscribeOnce(eventName, callback);
}
onProcessEndEventReached(endEventId, callback) {
const eventName = index_1.eventAggregatorSettings.messagePaths.endEventFinished.replace(index_1.eventAggregatorSettings.messageParams.processInstanceId, this.processInstanceId);
const subscription = EventAggregator_1.default.subscribe(eventName, (message) => {
const isExpectedEndEvent = message.flowNodeId === endEventId;
if (isExpectedEndEvent) {
EventAggregator_1.default.unsubscribe(subscription);
callback(message);
}
});
return subscription;
}
onTerminateEndEventReached(callback) {
const eventName = index_1.eventAggregatorSettings.messagePaths.terminateEndEventReached.replace(index_1.eventAggregatorSettings.messageParams.processInstanceId, this.processInstanceId);
return EventAggregator_1.default.subscribeOnce(eventName, callback);
}
onProcessError(callback) {
const eventName = index_1.eventAggregatorSettings.messagePaths.processInstanceWithIdErrored.replace(index_1.eventAggregatorSettings.messageParams.processInstanceId, this.processInstanceId);
return EventAggregator_1.default.subscribeOnce(eventName, callback);
}
onKillProcess(callback) {
const eventName = index_1.eventAggregatorSettings.messagePaths.killProcessInstanceWithId.replace(index_1.eventAggregatorSettings.messageParams.processInstanceId, this.processInstanceId);
return EventAggregator_1.default.subscribeOnce(eventName, callback);
}
onProcessKilled(callback) {
const eventName = index_1.eventAggregatorSettings.messagePaths.processInstanceWithIdKilled.replace(index_1.eventAggregatorSettings.messageParams.processInstanceId, this.processInstanceId);
return EventAggregator_1.default.subscribeOnce(eventName, callback);
}
onOwnerChanged(callback) {
const eventName = index_1.eventAggregatorSettings.messagePaths.processInstanceOwnerChanged.replace(index_1.eventAggregatorSettings.messageParams.processInstanceId, this.processInstanceId);
return EventAggregator_1.default.subscribe(eventName, callback);
}
buildRuntimeExpressionDataForFlowNodeInstance(config) {
const dataObjectValues = this.dataObjectFacade.getCurrentValues();
const currentDataObjectValue = this.dataObjectFacade.getValue(config.dataObjectId) ?? {};
const currentFlowNodeMetadata = processcube_engine_sdk_1.FlowNodeViewModelFactory.getViewModel(config.currentFlowNode, {
processModelId: this.processModelId,
processModelName: this.processModelName,
processModelVersion: this.processModelVersion,
});
const expressionPayload = {
correlationId: this.correlationId,
correlationMetadata: {
...this.correlationMetaData,
id: this.correlationId,
},
currentDataObject: currentDataObjectValue,
currentFlowNode: currentFlowNodeMetadata,
dataObjects: dataObjectValues,
identity: {
token: this.owner.token,
userEmail: this.owner.userEmail,
userId: this.owner.userId,
userName: this.owner.userName,
userToken: this.owner.token,
claims: this.owner.claims,
},
processModel: {
definitionId: this.processDefinitionId,
definitionHash: this.processDefinitionHash,
id: this.processModelId,
name: this.processModelName,
version: this.processModelVersion,
},
processInstanceMetadata: {
...this.processInstanceMetaData,
id: this.processInstanceId,
parentProcessInstanceId: this.parentProcessInstance?.getProcessInstanceId(),
},
previousFlowNodeId: config.previousFlowNode?.id,
token: {
current: config.currentToken,
},
allowNonObjectResults: config.allowNonObjectResults ?? false,
additionalProperties: config.additionalProperties,
};
return expressionPayload;
}
async configure(identity, options, processInstanceData) {
const internalIdentity = this.identityService.getInternalIdentity();
let processDefinition;
if (processInstanceData != undefined) {
// Configure for resuming
this.correlationId = processInstanceData.correlationId;
this.processInstanceId = processInstanceData.processInstanceId;
this.state = processInstanceData.state;
this.startedByRootAccessToken = processInstanceData.startedByRootAccessToken;
this.initialTokenPayload = processInstanceData.startToken ?? {};
this.processInstanceMetaData = processInstanceData.metadata;
this.startedAt = processInstanceData.createdAt;
this.embeddedProcessModelId = processInstanceData.embeddedProcessModelId;
processDefinition = await this.processDefinitionMediator.getByHash(internalIdentity, processInstanceData.hash, true);
}
else {
// Configure for regular execution
this.correlationId = options.correlationId || uuid.v4();
this.state = processcube_engine_sdk_1.ProcessInstanceState.running;
this.startedByRootAccessToken = this.identityService.isRootAccessToken(identity.token);
this.initialTokenPayload = options.initialToken ?? {};
this.embeddedProcessModelId = options.embeddedProcessModelId;
processDefinition = await this.processDefinitionMediator.getByProcessModelId(internalIdentity, options.processModelId);
}
const dataStoreRefs = processDefinition.processes.flatMap((process) => process.dataStoreReferences);
let processModel = processDefinition.processes.find((process) => process.id === options.processModelId);
processModel.dataStoreReferences = dataStoreRefs;
if (options.embeddedProcessModelId) {
const subProcessActivity = processModel.getAllEmbeddedSubProcesses().find((esp) => esp.id === options.embeddedProcessModelId);
if (!subProcessActivity) {
throw new Error(`Could not find embedded SubProcess with id ${options.embeddedProcessModelId} in Model ${processModel.id}`);
}
this.processModelFacade = new processcube_engine_sdk_1.ProcessModelFacade(processModel, options.parentProcessModelFacade);
const laneName = this.processModelFacade.getLaneForFlowNode(subProcessActivity.id)?.name;
const subProcessModel = new processcube_engine_sdk_1.Model.Process();
subProcessModel.id = processModel.id;
subProcessModel.version = processModel.version;
subProcessModel.associations = subProcessActivity.associations;
subProcessModel.dataObjectReferences = subProcessActivity.dataObjectReferences;
subProcessModel.dataObjects = subProcessActivity.dataObjects;
subProcessModel.dataStoreReferences = [...(subProcessActivity.dataStoreReferences ?? []), ...dataStoreRefs];
subProcessModel.documentation = subProcessActivity.documentation;
subProcessModel.extensionElements = subProcessActivity.extensionElements;
subProcessModel.flowNodes = subProcessActivity.flowNodes;
subProcessModel.laneSet = laneName
? { id: laneName, lanes: [{ id: laneName, name: laneName, flowNodeReferences: subProcessModel.flowNodes.map((fn) => fn.id) }] }
: subProcessActivity.laneSet;
subProcessModel.name = subProcessActivity.name;
subProcessModel.sequenceFlows = subProcessActivity.sequenceFlows;
subProcessModel.isExecutable = true;
processModel = subProcessModel;
}
this.owner = identity;
if (!this.owner.claims) {
try {
this.owner = await this.identityService.getIdentity({ token: identity.token, userId: identity.userId, skipValidation: true });
}
catch {
// This might fail if a Process that was started with the old defaultRootAccessToken gets resumed after the defaultRootAccessToken was changed.
}
}
this.processDefinitionId = processDefinition.id;
this.processDefinitionHash = processDefinition.hash;
this.processModelId = processModel.id;
this.processModelName = processModel.name;
this.processModelVersion = processModel.version;
await this.retrieveCorrelationMetadata();
this.processModelFacade = new processcube_engine_sdk_1.ProcessModelFacade(processModel, options.parentProcessModelFacade);
await this.dataObjectFacade.configure(this.processInstanceId, processModel.dataObjectReferences.map((dataObject) => dataObject.id), options.parentDataObjectFacade);
this.logger = new processcube_engine_sdk_1.Logger('process_instance', {
correlationId: this.correlationId,
processInstanceId: this.processInstanceId,
});
const ownerChangedEvent = index_1.eventAggregatorSettings.messagePaths.changeProcessInstanceOwner.replace(index_1.eventAggregatorSettings.messageParams.processInstanceId, this.processInstanceId);
const correlationMetadataChangedEvent = index_1.eventAggregatorSettings.messagePaths.changeCorrelationMetadata.replace(index_1.eventAggregatorSettings.messageParams.correlationId, this.correlationId);
this.ownerChangedSubscription = EventAggregator_1.default.subscribe(ownerChangedEvent, (message) => this.changeOwner(message.newOwner, message.eventId));
this.correlationMetadataChangedSubscription = EventAggregator_1.default.subscribe(correlationMetadataChangedEvent, (message) => {
this.updateCorrelationMetadata(message.changedMetadata);
EventAggregator_1.default.acknowledgeEvent(message.eventId);
});
}
async save(options) {
this.logProcessStarting(options.initialToken);
this.sendProcessStartingNotification();
const startEvent = options.startEventId ? this.processModelFacade.getStartEventById(options.startEventId) : this.processModelFacade.getSingleStartEvent();
const createRequest = {
identity: this.owner,
startedByRootAccessToken: this.owner.token === this.identityService.getRootAccessIdentity().token,
correlationId: this.correlationId,
processInstanceId: this.processInstanceId,
processDefinitionId: this.processDefinitionId,
processModelId: this.processModelId,
processModelName: this.processModelName,
processModelVersion: this.processModelVersion?.trim() || null,
processModelHash: this.processDefinitionHash,
parentProcessInstanceId: this.parentProcessInstance?.getProcessInstanceId(),
startEventId: startEvent.id,
startEventType: this.getEventType(startEvent),
startToken: options.initialToken ?? {},
triggeredByFlowNodeInstanceId: options.triggeredByFlowNodeInstanceId,
embeddedProcessModelId: options.embeddedProcessModelId,
};
await this.processInstanceDatabaseAdapter.create(createRequest);
this.logProcessStarted(options.initialToken);
}
async executeProcess(options) {
try {
await this.seedDataObjectsWithIntialValues();
const startEvent = options.startEventId ? this.processModelFacade.getStartEventById(options.startEventId) : this.processModelFacade.getSingleStartEvent();
const startEventHandler = this.flowNodeHandlerFactory.create(startEvent, this);
ProcessInstanceStore_1.default.hook(this);
// Because of the usage of Promise-Chains, we only need to run the StartEvent and wait for the ProcessInstance to run its course.
await startEventHandler.execute(undefined, undefined, undefined, options.triggeredByFlowNodeInstanceId);
return await this.finish();
}
catch (error) {
// This is caused by the abortable promises and can be ignored.
if (error.type === 'abort') {
return await this.finish();
}
await this.finishWithError(error);
throw error;
}
}
async resumeProcess(flowNodeInstances, flowNodeInstancesToResume) {
try {
ProcessInstanceStore_1.default.hook(this);
if (flowNodeInstancesToResume && flowNodeInstancesToResume.length > 0) {
this.logger.info(`Resuming process instance ${this.processInstanceId} at Flow Node Instances ${flowNodeInstancesToResume.map((fni) => fni.flowNodeInstanceId)}`, {
processModelId: this.processModelId,
processInstanceId: this.processInstanceId,
correlationId: this.correlationId,
parentProcessInstanceId: this.parentProcessInstance?.getProcessInstanceId(),
embeddedProcessModelId: this.embeddedProcessModelId,
});
await this.resumeProcessAtExitPoints(flowNodeInstances, flowNodeInstancesToResume);
}
else {
const errorMessage = `Could not determine the Flow Node Instances at which \`${this.processInstanceId}\` was stopped. Resuming from the Start Event.`;
this.logger.warn(errorMessage, {
processModelId: this.processModelId,
processInstanceId: this.processInstanceId,
correlationId: this.correlationId,
parentProcessInstanceId: this.parentProcessInstance?.getProcessInstanceId(),
embeddedProcessModelId: this.embeddedProcessModelId,
});
await this.resumeProcessFromStart(flowNodeInstances);
}
return await this.finish();
}
catch (error) {
// This is caused by the abortable promises and can be ignored.
if (error.type === 'abort') {
return await this.finish();
}
await this.finishWithError(error);
throw error;
}
}
async resumeProcessFromStart(allFlowNodeInstances) {
const startEventInstance = allFlowNodeInstances.find((flowNodeInstance) => flowNodeInstance.flowNodeType === processcube_engine_sdk_1.BpmnType.startEvent);
const startEventModel = this.processModelFacade.getFlowNodeById(startEventInstance.flowNodeId);
const startEventHandler = this.flowNodeHandlerFactory.create(startEventModel, this);
await startEventHandler.resume(startEventInstance, allFlowNodeInstances);
}
async resumeProcessAtExitPoints(allFlowNodeInstances, flowNodeInstancesToResume) {
await this.buildResumptionPromises(allFlowNodeInstances, flowNodeInstancesToResume);
}
async buildResumptionPromises(allFlowNodeInstances, flowNodeInstancesToResume) {
const partionatedResumableFlowNodeInstances = this.groupFlowNodeInstancesByResumingStrategy(flowNodeInstancesToResume);
const nonActivityResumptionPromises = partionatedResumableFlowNodeInstances.nonActivityFlowNodeInstances.map(async (flowNodeInstance) => {
const flowNode = this.processModelFacade.getFlowNodeById(flowNodeInstance.flowNodeId);
const flowNodeHandler = this.flowNodeHandlerFactory.create(flowNode, this);
await flowNodeHandler.resume(flowNodeInstance, allFlowNodeInstances);
});
const defaultActivityResumptionPromises = partionatedResumableFlowNodeInstances.defaultActivityFlowNodeInstances.map(async (flowNodeInstance) => {
const flowNode = this.processModelFacade.getFlowNodeById(flowNodeInstance.flowNodeId);
const activityHandler = this.flowNodeHandlerFactory.create(flowNode, this);
await activityHandler.resume(flowNodeInstance, allFlowNodeInstances);
});
const multiInstancesActivityResumptionPromises = Object.entries(partionatedResumableFlowNodeInstances.multiInstanceActivityFlowNodeInstances).map(async (entry) => {
const flowNodeInstances = entry[1];
const firstFlowNodeInstance = flowNodeInstances[0];
const flowNode = this.processModelFacade.getFlowNodeById(firstFlowNodeInstance.flowNodeId);
const activityHandler = this.flowNodeHandlerFactory.create(flowNode, this);
await activityHandler.resume(flowNodeInstances, allFlowNodeInstances);
});
await Promise.all([...nonActivityResumptionPromises, ...defaultActivityResumptionPromises, ...multiInstancesActivityResumptionPromises]);
}
async finish() {
EventAggregator_1.default.unsubscribe(this.ownerChangedSubscription);
EventAggregator_1.default.unsubscribe(this.correlationMetadataChangedSubscription);
await this.cleanup();
this.state = processcube_engine_sdk_1.ProcessInstanceState.finished;
const finalResult = await this.processTokenService.getFinalResult(this.processInstanceId);
const finalFlowNode = this.processModelFacade.getFlowNodeById(finalResult.flowNodeId);
this.finishedAt = dayjs_1.default.utc().toDate();
await this.processInstanceDatabaseAdapter.finish(this.processInstanceId, {
endEventId: finalFlowNode.id,
endEventType: this.getEventType(finalFlowNode),
endEventToken: finalResult.result,
});
ProcessInstanceStore_1.default.unhook(this.processInstanceId);
this.logProcessFinished(finalResult.result);
this.sendFinishedNotification(finalResult, finalFlowNode);
return finalResult;
}
async finishWithError(error) {
EventAggregator_1.default.unsubscribe(this.ownerChangedSubscription);
EventAggregator_1.default.unsubscribe(this.correlationMetadataChangedSubscription);
const isTerminationEvent = error.additionalInformation?.processInstanceWasTerminated === true;
await this.terminateSubprocesses();
await this.cleanup();
// Note: When running into ErrorEndEvents or TerminateEndEvents,
// the final Flow Node to store its results in the ProcessTokenFacade may not have been the End Event,
// if other Flow Node Instances needed to be terminated afterwards.
const endEventInstances = await this.flowNodeInstanceDatabaseAdapter.query({
flowNodeType: processcube_engine_sdk_1.BpmnType.endEvent,
processInstanceId: this.processInstanceId,
});
let endEventData = undefined;
if (endEventInstances.flowNodeInstances.length > 0) {
const endEventInstance = endEventInstances.flowNodeInstances.pop();
endEventData = {
endEventId: endEventInstance.flowNodeId,
endEventType: endEventInstance.eventType,
endEventToken: endEventInstance.endToken ?? {},
};
}
if (isTerminationEvent) {
this.state = processcube_engine_sdk_1.ProcessInstanceState.terminated;
await this.processInstanceDatabaseAdapter.terminate(this.processInstanceId, error, endEventData, true);
ProcessInstanceStore_1.default.unhook(this.processInstanceId);
this.logProcessTerminated(error);
this.sendTerminationNotification(error);
}
else {
this.state = processcube_engine_sdk_1.ProcessInstanceState.error;
await this.processInstanceDatabaseAdapter.finishWithError(this.processInstanceId, error, endEventData);
ProcessInstanceStore_1.default.unhook(this.processInstanceId);
this.logProcessError(error);
this.sendErrorNotification(error);
}
}
async cleanup() {
const disposeByType = async (flowNode, baseKey) => {
if (this.container.isBound(`${baseKey}-${this.processInstanceId}-${flowNode.id}`)) {
await this.container.unbind(`${baseKey}-${this.processInstanceId}-${flowNode.id}`);
}
};
await Promise.all([
...this.processModelFacade.getFlowNodesByType(processcube_engine_sdk_1.BpmnType.parallelGateway).map(async (flowNode) => disposeByType(flowNode, 'ParallelJoinGatewayHandlerInstance')),
...this.processModelFacade.getFlowNodesByType(processcube_engine_sdk_1.BpmnType.inclusiveGateway).map(async (flowNode) => disposeByType(flowNode, 'InclusiveJoinGatewayHandlerInstance')),
...this.processModelFacade.getFlowNodesByType(processcube_engine_sdk_1.BpmnType.intermediateCatchEvent).map(async (flowNode) => disposeByType(flowNode, 'CatchEventHandlerInstance')),
...this.processModelFacade.getFlowNodesByType(processcube_engine_sdk_1.BpmnType.receiveTask).map(async (flowNode) => disposeByType(flowNode, 'ReceiveTaskHandlerInstance')),
]);
}
async terminateSubprocesses() {
const result = await this.processInstanceDatabaseAdapter.query({ parentProcessInstanceId: this.processInstanceId, state: processcube_engine_sdk_1.ProcessInstanceState.running });
if (result.processInstances?.length === 0) {
return;
}
for (const subprocess of result.processInstances) {
const terminateSubProcessEvent = index_1.eventAggregatorSettings.messagePaths.killProcessInstanceWithId.replace(index_1.eventAggregatorSettings.messageParams.processInstanceId, subprocess.processInstanceId);
const subprocessIdentity = { token: subprocess.ownerToken, userId: subprocess.ownerId };
const terminationError = new processcube_engine_sdk_1.GoneError(`Process was terminated by parent ProcessInstance ${this.processInstanceId}`);
terminationError.additionalInformation = {
processInstanceWasTerminated: true,
};
const subprocessTerminationMessage = {
correlationId: subprocess.correlationId,
processDefinitionId: subprocess.processDefinitionId,
processModelId: subprocess.processModelId,
embeddedProcessModelId: subprocess.embeddedProcessModelId,
processInstanceId: subprocess.processInstanceId,
parentProcessInstanceId: this.parentProcessInstance?.getProcessInstanceId(),
processInstanceOwner: subprocessIdentity,
currentToken: terminationError,
};
EventAggregator_1.default.publish(terminateSubProcessEvent, subprocessTerminationMessage);
}
}
changeOwner(newOwner, eventId) {
this.owner = newOwner;
EventAggregator_1.default.acknowledgeEvent(eventId);
this.sendProcessOwnerChangedNotification();
}
updateCorrelationMetadata(newMetadata) {
this.correlationMetaData = {
...this.correlationMetaData,
...newMetadata,
};
}
sendProcessStartingNotification() {
EventAggregator_1.default.publish(index_1.eventAggregatorSettings.messagePaths.processStarting, this.getNotificationPayload());
}
sendProcessResumedNotification() {
EventAggregator_1.default.publish(index_1.eventAggregatorSettings.messagePaths.processResumed, this.getNotificationPayload());
}
sendProcessOwnerChangedNotification() {
const processInstanceNotification = index_1.eventAggregatorSettings.messagePaths.processInstanceOwnerChanged.replace(index_1.eventAggregatorSettings.messageParams.processInstanceId, this.processInstanceId);
const processOwnerChangedMessage = this.getNotificationPayload();
// Instance specific Event
EventAggregator_1.default.publish(processInstanceNotification, processOwnerChangedMessage);
// Generic event
EventAggregator_1.default.publish(index_1.eventAggregatorSettings.messagePaths.processOwnerChanged, processOwnerChangedMessage);
}
sendFinishedNotification(resultToken, finalFlowNode) {
const processInstanceEndedNotification = index_1.eventAggregatorSettings.messagePaths.processInstanceWithIdEnded.replace(index_1.eventAggregatorSettings.messageParams.processInstanceId, this.processInstanceId);
const processInstanceFinishedMessage = {
...this.getNotificationPayload(resultToken.result),
flowNodeId: resultToken.flowNodeId,
flowNodeName: finalFlowNode.name,
flowNodeInstanceId: resultToken.flowNodeInstanceId,
eventType: this.getEventType(finalFlowNode),
eventName: finalFlowNode.signalEventDefinition?.name ?? finalFlowNode.messageEventDefinition?.name,
};
// Instance specific Event
EventAggregator_1.default.publish(processInstanceEndedNotification, processInstanceFinishedMessage);
// Generic event
EventAggregator_1.default.publish(index_1.eventAggregatorSettings.messagePaths.processEnded, processInstanceFinishedMessage);
}
sendErrorNotification(error) {
const processInstanceErrorNotification = index_1.eventAggregatorSettings.messagePaths.processInstanceWithIdErrored.replace(index_1.eventAggregatorSettings.messageParams.processInstanceId, this.processInstanceId);
const processInstanceErrorMessage = this.getNotificationPayload({
name: error.name,
code: error.code,
message: error.message,
stack: error.stack,
category: error.category,
...error,
});
// Instance specific Event
EventAggregator_1.default.publish(processInstanceErrorNotification, processInstanceErrorMessage);
// Generic event
EventAggregator_1.default.publish(index_1.eventAggregatorSettings.messagePaths.processError, processInstanceErrorMessage);
}
sendTerminationNotification(error) {
const processInstanceTerminatedNotification = index_1.eventAggregatorSettings.messagePaths.processInstanceWithIdKilled.replace(index_1.eventAggregatorSettings.messageParams.processInstanceId, this.processInstanceId);
const processTerminatedMessage = {
...this.getNotificationPayload({
name: error.name,
code: error.code,
message: error.message,
stack: error.stack,
category: error.category,
...error,
}),
flowNodeId: error.additionalInformation?.flowNodeId,
flowNodeName: error.additionalInformation?.flowNodeName,
flowNodeInstanceId: error.additionalInformation?.flowNodeInstanceId,
previousFlowNode: error.additionalInformation?.previousFlowNode,
eventType: error.additionalInformation?.eventType,
};
// Instance specific Event
EventAggregator_1.default.publish(processInstanceTerminatedNotification, processTerminatedMessage);
// Generic event
EventAggregator_1.default.publish(index_1.eventAggregatorSettings.messagePaths.processTerminated, processTerminatedMessage);
}
logProcessStarting(payload) {
this.writeLog(processcube_engine_sdk_1.EngineEventType.OnProcessStarting, processcube_engine_sdk_1.LogLevel.info, payload);
}
logProcessStarted(payload) {
this.writeLog(processcube_engine_sdk_1.EngineEventType.OnProcessStarted, processcube_engine_sdk_1.LogLevel.info, payload);
}
logProcessResumed(payload) {
this.writeLog(processcube_engine_sdk_1.EngineEventType.OnProcessResumed, processcube_engine_sdk_1.LogLevel.info, payload);
}
logProcessFinished(payload) {
this.writeLog(processcube_engine_sdk_1.EngineEventType.OnProcessFinished, processcube_engine_sdk_1.LogLevel.info, payload);
}
logProcessError(error) {
this.writeLog(processcube_engine_sdk_1.EngineEventType.onProcessError, processcube_engine_sdk_1.LogLevel.error, error);
}
logProcessTerminated(payload) {
this.writeLog(processcube_engine_sdk_1.EngineEventType.OnProcessTerminated, processcube_engine_sdk_1.LogLevel.error, payload);
}
writeLog(event, logLevel, payload = undefined) {
const baseExpressionDataForProcessInstance = {
correlationId: this.correlationId,
correlationMetadata: {
...this.correlationMetaData,
id: this.correlationId,
},
processModel: {
definitionId: this.processDefinitionId,
definitionHash: this.processDefinitionHash,
id: this.processModelId,
name: this.processModelName,
version: this.processModelVersion,
},
processInstanceMetadata: {
...this.processInstanceMetaData,
id: this.processInstanceId,
},
dataObjects: this.dataObjectFacade.getCurrentValues(),
identity: {
token: this.owner.token,
userId: this.owner.userId,
userToken: this.owner.token,
claims: this.owner.claims,
},
allowNonObjectResults: true,
};
this.eventMiddlewareHandler.triggerEvent((0, lodash_clonedeep_1.default)({
eventType: event,
timestamp: dayjs_1.default.utc().toDate(),
processModelId: this.processModelId,
embeddedProcessModelId: this.embeddedProcessModelId,
processModelName: this.processModelName,
processDefinitionId: this.processDefinitionId,
processDefinitionHash: this.processDefinitionHash,
processInstanceId: this.processInstanceId,
parentProcessInstanceId: this.parentProcessInstance?.getProcessInstanceId(),
correlationId: this.correlationId,
tokenPayload: payload,
logLevel: logLevel,
processStartedAt: this.startedAt,
processFinishedAt: this.finishedAt,
writtenCorrelationMetadata: baseExpressionDataForProcessInstance.correlationMetadata,
writtenProcessInstanceMetadata: baseExpressionDataForProcessInstance.processInstanceMetadata,
writtenDataObjectValues: baseExpressionDataForProcessInstance.dataObjects,
}), baseExpressionDataForProcessInstance);
}
checkForFlowNodeInstancesWithMultipleSuccessors(flowNodeInstances) {
// Gateways are not ellible for this check, because they are allowed to have multiple incoming flows
// Boundary Events are not elligble for this check, because you can attach multiple Boundary Events to the same activity
const flowNodeInstancesToCheck = flowNodeInstances.filter((flowNodeInstance) => {
return (flo