UNPKG

@5minds/processcube_engine

Version:

The ProcessCube Engine. Stores and executes BPMNs.

908 lines • 63.3 kB
"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