UNPKG

@5minds/processcube_engine

Version:

The ProcessCube Engine. Stores and executes BPMNs.

892 lines (891 loc) • 67 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 __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var FlowNodeInstanceDatabaseAdapter_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.FlowNodeInstanceDatabaseAdapter = void 0; const async_lock_1 = __importDefault(require("async-lock")); const inversify_1 = require("inversify"); const sequelize_1 = require("sequelize"); const uuid = __importStar(require("uuid")); const processcube_engine_sdk_1 = require("@5minds/processcube_engine_sdk"); const index_1 = require("../../Contracts/InternalDataModels/index"); const MonitoringManager_1 = require("../MonitoringManager"); const BaseDatabaseAdapter_1 = require("./BaseDatabaseAdapter"); const Compression_1 = require("./Lib/Compression"); const FlowNodeInstanceFactory_1 = require("./Lib/FlowNodeInstanceFactory"); const SearchQueryBuilder_1 = require("./Lib/SearchQueryBuilder"); const SequelizeConnectionManager_1 = require("./Lib/SequelizeConnectionManager"); const index_2 = require("./Models/index"); const asyncLocker = new async_lock_1.default({ maxPending: 100000 }); let FlowNodeInstanceDatabaseAdapter = FlowNodeInstanceDatabaseAdapter_1 = class FlowNodeInstanceDatabaseAdapter extends BaseDatabaseAdapter_1.DatabaseAdapter { async initialize() { await super.initialize('database_adapter:flow_node_instances'); } async findByInstanceId(flowNodeInstanceId) { const result = await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => index_2.FlowNodeInstanceModel.findOne({ where: { flowNodeInstanceId } }), `get flow node instance by id ${flowNodeInstanceId}`); return (0, FlowNodeInstanceFactory_1.createFlowNodeInstanceFromModel)(result); } async query(query, offset = 0, limit = 0, sort, includeDeleted = false, includeCount = false) { const functionReporter = MonitoringManager_1.MonitoringManager.getNewFunctionReporter('FlowNodeInstanceDatabaseAdapter.query', { queryKeys: Object.keys(query) ?? [], offset, limit, includeCount, }, { queryValues: Object.values(query) ?? [], }); const includeOnlyNonDeleted = includeDeleted ? {} : { [sequelize_1.Op.or]: [{ '$processInstance.deleted$': false }, { '$processInstance.deleted$': { [sequelize_1.Op.is]: null } }], }; const hasQuery = Object.keys(query).length > 0; const whereClause = hasQuery ? { [sequelize_1.Op.and]: [includeOnlyNonDeleted, (0, SearchQueryBuilder_1.buildFlowNodeLaneQuery)(query)], } : includeOnlyNonDeleted; const includes = this.determineRequiredJoinsOnMetadataTables(query.flowNodeType); includes.push({ model: index_2.MultiInstanceMetadataModel, as: 'multiInstanceMetadata', required: false, }, { model: index_2.ProcessInstanceModel, as: 'processInstance', attributes: ['deleted', 'processModelName'], required: false, }); const options = { where: whereClause, include: includes, order: [[sort?.sortBy ?? 'createdAt', sort?.sortDir ?? 'DESC']], ...(0, SearchQueryBuilder_1.buildPagination)(offset, limit), subQuery: false, }; let count; if (includeCount) { count = await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => index_2.FlowNodeInstanceModel.count(options), 'query flow node instance count'); } const result = await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => index_2.FlowNodeInstanceModel.findAll(options), 'query flow node instances'); const flowNodeInstances = []; for (const rawFlowNodeInstance of result) { const flowNodeInstance = await (0, FlowNodeInstanceFactory_1.createFlowNodeInstanceFromModel)(rawFlowNodeInstance); flowNodeInstances.push(flowNodeInstance); } functionReporter.finish(); return { flowNodeInstances: flowNodeInstances, totalCount: count, }; } async getFlowNodeExecutionCount(flowNodeId, processInstanceId) { const flowNodeExecutionCount = await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => index_2.FlowNodeInstanceModel.count({ where: { processInstanceId: processInstanceId, flowNodeId: flowNodeId, }, }), 'query FlowNodeExecutionCount'); return flowNodeExecutionCount; } async getFlowNodeInstancesForProcessInstanceIds(processInstanceId) { const results = await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { return index_2.FlowNodeInstanceModel.findAll({ attributes: ['processInstanceId', 'flowNodeId', 'flowNodeName', 'flowNodeLane'], where: { processInstanceId: { [sequelize_1.Op.in]: processInstanceId, }, }, group: ['processInstanceId', 'flowNodeId', 'flowNodeName', 'flowNodeLane'], }); }, `Get executed Flow Nodes for Process Instance ${processInstanceId.join(', ')}`); return results.map((result) => result.toJSON()); } async getActiveMessageCatchEventsInProcessModel(processModelId, messageName) { const where = { [sequelize_1.Op.or]: [ { flowNodeType: processcube_engine_sdk_1.BpmnType.receiveTask }, { flowNodeType: { [sequelize_1.Op.in]: [processcube_engine_sdk_1.BpmnType.boundaryEvent, processcube_engine_sdk_1.BpmnType.intermediateCatchEvent] }, eventType: processcube_engine_sdk_1.EventType.messageEvent, }, ], processModelId: processModelId, state: { [sequelize_1.Op.in]: [processcube_engine_sdk_1.FlowNodeInstanceState.running, processcube_engine_sdk_1.FlowNodeInstanceState.suspended] }, '$catchEventInstanceMetaInfo.eventName$': messageName, }; const result = await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => index_2.FlowNodeInstanceModel.findAll({ include: [ { model: index_2.CatchEventInstanceMetaInfoModel, as: 'catchEventInstanceMetaInfo', required: true, }, ], where: where, }), `get Message Catch Events waiting for message ${messageName} in ProcessModel ${processModelId}`); const flowNodeInstances = []; for (const rawFlowNodeInstance of result) { const flowNodeInstance = await (0, FlowNodeInstanceFactory_1.createFlowNodeInstanceFromModel)(rawFlowNodeInstance); flowNodeInstances.push(flowNodeInstance); } return flowNodeInstances; } // Finishes all unfinished Flow Node Instances that belong to a finished Process Instance. // Should be run only once, during engine startup. async cleanupOrphanedFlowNodeInstances() { const dbRequestId = uuid.v4(); this.logger.info('Running Cleanup on Flow Node Instances and External Tasks.', { dbRequestId: dbRequestId }); return (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { const cleanupFlowNodeInstancesQuery = (0, SearchQueryBuilder_1.getCleanupOrphanedFlowNodeInstancesQuery)(this.sequelizeInstance.getDialect(), this.config.schema); const cleanupExternalTasksQuery = (0, SearchQueryBuilder_1.getCleanupOrphanedFlowNodeInstancesQuery)(this.sequelizeInstance.getDialect(), this.config.schema); const transaction = await this.sequelizeInstance.transaction(); try { this.logger.debug('Cleaning up orphaned Flow Node Instances.', { dbRequestId: dbRequestId }); const [fniResults, fniMeta] = await this.sequelizeInstance.query(cleanupFlowNodeInstancesQuery, { transaction, }); // NOTE: // For MSQ SQL "fniMeta" will be a number, showing the number of affected rows. // For Postgres, it will be an object with a property "rowCount" . // For SQLite, this won't do anything, because there will be no return values whatsoever. const fniRowCount = typeof fniMeta == 'number' ? fniMeta : fniMeta.rowCount; if (fniRowCount > 0) { this.logger.info(`Cleaned up a total of ${fniRowCount} orphaned Flow Node Instances.`, { dbRequestId: dbRequestId }); } this.logger.debug('Cleaning up orphaned External Tasks.', { dbRequestId: dbRequestId }); const [etResults, etMeta] = await this.sequelizeInstance.query(cleanupExternalTasksQuery, { transaction, }); const etRowCount = typeof etMeta == 'number' ? etMeta : etMeta.rowCount; if (etRowCount > 0) { this.logger.info(`Cleaned up a total of ${etRowCount} orphaned ExternalTasks.`, { dbRequestId: dbRequestId }); } await transaction.commit(); } catch (error) { this.logger.error(`Failed to terminate orphaned Flow Node Instances.`, { dbRequestId: dbRequestId, err: error, }); await transaction.rollback(); } }, `Cleaning up orphaned Flow Node Instances and External Tasks`, undefined, undefined, dbRequestId); } async persistOnEnter(payload) { await asyncLocker.acquire(`${payload.flowNodeInstanceId}-change-state`, async () => { this.logger.trace(`Persisting new Flow Node Instance ${payload.flowNodeInstanceId}`, payload); return (0, SequelizeConnectionManager_1.executeWithRetry)(this.execPersistOnEnter.bind(this, payload), `persist flow node '${payload.flowNodeId}' as instance '${payload.flowNodeInstanceId}' on enter`); }); } async batchPersistOnEnter(payloads) { await asyncLocker.acquire('batch-persist-on-enter', async () => { this.logger.trace(`Persisting ${payloads.length} new Flow Node Instances`, payloads); return (0, SequelizeConnectionManager_1.executeWithRetry)(this.execBatchPersistOnEnter.bind(this, payloads), `persist ${payloads.length} flow nodes on enter`); }); } async persistOnSuspend(flowNodeInstanceId, data) { await asyncLocker.acquire(`${flowNodeInstanceId}-change-state`, async () => { this.logger.trace(`Changing state of Flow Node Instance ${flowNodeInstanceId} to 'suspended'`, { flowNodeInstanceId: flowNodeInstanceId, ...data }); await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { await this.persistOnStateChange(flowNodeInstanceId, processcube_engine_sdk_1.FlowNodeInstanceState.suspended, data); }, `persist flow node instance '${flowNodeInstanceId}' on suspend`); }); } async persistOnExit(flowNodeInstanceId, data) { await asyncLocker.acquire(`${flowNodeInstanceId}-change-state`, async () => { this.logger.trace(`Changing state of Flow Node Instance ${flowNodeInstanceId} to 'finished'`, { flowNodeInstanceId: flowNodeInstanceId, ...data }); await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { await this.persistOnStateChange(flowNodeInstanceId, processcube_engine_sdk_1.FlowNodeInstanceState.finished, data); }, `persist flow node instance '${flowNodeInstanceId}' on exit`); }); } async persistOnCancel(flowNodeInstanceId, data) { await asyncLocker.acquire(`${flowNodeInstanceId}-change-state`, async () => { this.logger.trace(`Changing state of Flow Node Instance ${flowNodeInstanceId} to 'canceled'`, { flowNodeInstanceId: flowNodeInstanceId, ...data }); await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { await this.persistOnStateChange(flowNodeInstanceId, processcube_engine_sdk_1.FlowNodeInstanceState.canceled, data); }, `persist flow node instance '${flowNodeInstanceId}' on cancel`); }); } async persistOnError(flowNodeInstanceId, data) { await asyncLocker.acquire(`${flowNodeInstanceId}-change-state`, async () => { this.logger.trace(`Changing state of Flow Node Instance ${flowNodeInstanceId} to 'error'`, { flowNodeInstanceId: flowNodeInstanceId, ...data }); await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { await this.persistOnStateChange(flowNodeInstanceId, processcube_engine_sdk_1.FlowNodeInstanceState.error, data); }, `persist flow node instance '${flowNodeInstanceId}' on error`); }); } async persistOnTerminate(flowNodeInstanceId, data) { await asyncLocker.acquire(`${flowNodeInstanceId}-change-state`, async () => { this.logger.trace(`Changing state of Flow Node Instance ${flowNodeInstanceId} to 'terminated'`, { flowNodeInstanceId: flowNodeInstanceId, ...data }); await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { await this.persistOnStateChange(flowNodeInstanceId, processcube_engine_sdk_1.FlowNodeInstanceState.terminated, data); }, `persist flow node instance '${flowNodeInstanceId}' on terminate`); }); } async updateStartToken(flowNodeInstanceIds, tokenPayload) { const encodedPayload = this.config.compressProcessTokens ? undefined : (0, processcube_engine_sdk_1.serializeJson)(tokenPayload ?? {}); const compressedPayload = this.config.compressProcessTokens ? await (0, Compression_1.compressToken)(tokenPayload ?? {}) : undefined; await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { await index_2.FlowNodeInstanceModel.update({ startToken: encodedPayload, startTokenCompressed: compressedPayload, }, { where: { flowNodeInstanceId: { [sequelize_1.Op.in]: flowNodeInstanceIds, }, }, }); }, `update start token of flow node instances ${flowNodeInstanceIds.join(', ')}`); } async deleteByProcessDefinitionId(processDefinitionId) { await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { const deleteTransaction = await this.sequelizeInstance.transaction(); try { await FlowNodeInstanceDatabaseAdapter_1.deleteByProcessDefinitionId(processDefinitionId, deleteTransaction); await deleteTransaction.commit(); } catch (error) { await deleteTransaction.rollback(); throw error; } }, `delete flow node instances by process definition id '${processDefinitionId}'`); } /** * This is dangerous, because it can potentially break a process instance. * Deletion should usually be done in the context of deleting or resetting a Process Instance, or when undeploying a diagram. * Use for tests or cleanup procedures only. */ async UNSAFE_deleteById(flowNodeInstanceId) { await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { const deleteTransaction = await this.sequelizeInstance.transaction(); try { const flowNodeInstancesToRemove = await index_2.FlowNodeInstanceModel.findAll({ where: { flowNodeInstanceId: flowNodeInstanceId, }, transaction: deleteTransaction, attributes: ['flowNodeInstanceId'], }); await FlowNodeInstanceDatabaseAdapter_1.deleteFlowNodeInstancesInBatches(flowNodeInstancesToRemove, deleteTransaction); await deleteTransaction.commit(); } catch (error) { await deleteTransaction.rollback(); throw error; } }, `delete flow node instance '${flowNodeInstanceId}'`); } async saveMultiInstanceMetadata(multiInstanceMetadataId, startToken, endToken) { await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { const multiInstanceMetadata = await index_2.MultiInstanceMetadataModel.findOne({ where: { multiInstanceMetadataId: multiInstanceMetadataId, }, }); const serializedStartToken = startToken ? (0, processcube_engine_sdk_1.serializeJson)(startToken) : undefined; const serializedEndToken = endToken ? (0, processcube_engine_sdk_1.serializeJson)(endToken) : undefined; if (multiInstanceMetadata) { multiInstanceMetadata.startToken = serializedStartToken ?? multiInstanceMetadata.startToken; multiInstanceMetadata.endToken = serializedEndToken ?? multiInstanceMetadata.endToken; await multiInstanceMetadata.save(); } else { await index_2.MultiInstanceMetadataModel.create({ multiInstanceMetadataId: multiInstanceMetadataId, startToken: serializedStartToken, endToken: serializedEndToken, }); } }, `persist multi instance metadata for instance '${multiInstanceMetadataId}'`); } async loadFlowNodeInstancesForMultiInstance(multiInstanceMetadataId) { const options = { where: { multiInstanceMetadataId: multiInstanceMetadataId, }, include: [ { model: index_2.FlowNodeInstanceModel, as: 'triggeredByFlowNodeInstance', required: false, }, { model: index_2.CallActivityInstanceMetaInfoModel, as: 'callActivityInstanceMetaInfo', required: false, }, { model: index_2.SubprocessInstancesMetaInfoModel, as: 'subprocessInstanceMetaInfo', required: false, }, { model: index_2.CatchEventInstanceMetaInfoModel, as: 'catchEventInstanceMetaInfo', required: false, }, { model: index_2.ThrowEventInstanceMetaInfoModel, as: 'throwEventInstanceMetaInfo', required: false, }, { model: index_2.ManualTaskMetaInfoModel, as: 'manualTaskMetaInfo', required: false, }, { model: index_2.ExternalTaskModel, as: 'externalTaskData', required: false, }, { model: index_2.HttpServiceTaskModel, as: 'httpServiceTaskData', required: false, }, { model: index_2.UserTaskMetaInfoModel, as: 'userTaskMetaInfo', required: false, }, { model: index_2.MultiInstanceMetadataModel, as: 'multiInstanceMetadata', required: false, }, ], }; const result = await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => index_2.FlowNodeInstanceModel.findAll(options), 'load flow node instances for multi instance'); const flowNodeInstances = []; for (const rawFlowNodeInstance of result) { const flowNodeInstance = await (0, FlowNodeInstanceFactory_1.createFlowNodeInstanceFromModel)(rawFlowNodeInstance); flowNodeInstances.push(flowNodeInstance); } return flowNodeInstances; } async updateFlowNodesInProcessInstance(processInstanceIds, flowNodes) { await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { const updateTransaction = await this.sequelizeInstance.transaction(); try { for (const flowNode of flowNodes) { await index_2.FlowNodeInstanceModel.update({ flowNodeName: flowNode.name, flowNodeLane: flowNode.lane, }, { where: { processInstanceId: { [sequelize_1.Op.in]: processInstanceIds, }, flowNodeId: flowNode.id, }, transaction: updateTransaction, }); } await updateTransaction.commit(); } catch (error) { await updateTransaction.rollback(); throw error; } }, `update flow nodes for process instance '${processInstanceIds.join(', ')}'`); } static async reset(flowNodeInstanceId, transaction, isUnstartedSubProcess) { return (0, SequelizeConnectionManager_1.executeWithRetry)(this.execReset.bind(this, flowNodeInstanceId, transaction, isUnstartedSubProcess), `reset flow node instance '${flowNodeInstanceId}'`); } static async execReset(flowNodeInstanceId, transaction, isUnstartedSubProcess) { const queryParams = { where: { flowNodeInstanceId: flowNodeInstanceId, }, include: { model: index_2.CallActivityInstanceMetaInfoModel, as: 'callActivityInstanceMetaInfo', required: false, }, }; const flowNodeInstance = await index_2.FlowNodeInstanceModel.findOne(queryParams); if (!flowNodeInstance) { throw new processcube_engine_sdk_1.NotFoundError(`FlowNodeInstance with ID \`${flowNodeInstanceId}\` not found.`); } const hasChildProcess = flowNodeInstance.callActivityInstanceMetaInfo?.childProcessInstanceId; const isSubProcess = flowNodeInstance.flowNodeType === processcube_engine_sdk_1.BpmnType.subProcess; if (isSubProcess) { flowNodeInstance.state = isUnstartedSubProcess ? processcube_engine_sdk_1.FlowNodeInstanceState.running : processcube_engine_sdk_1.FlowNodeInstanceState.suspended; } else { flowNodeInstance.state = hasChildProcess ? processcube_engine_sdk_1.FlowNodeInstanceState.suspended : processcube_engine_sdk_1.FlowNodeInstanceState.running; } flowNodeInstance.error = null; flowNodeInstance.suspendedAt = null; flowNodeInstance.finishedAt = null; flowNodeInstance.endToken = null; await flowNodeInstance.save({ transaction: transaction }); const destroyOptions = { where: { flowNodeInstanceId: flowNodeInstanceId, }, transaction: transaction, }; await index_2.DataObjectModel.destroy(destroyOptions); await index_2.ExternalTaskModel.destroy(destroyOptions); await index_2.ManualTaskMetaInfoModel.destroy(destroyOptions); await index_2.UserTaskMetaInfoModel.destroy(destroyOptions); } static async deleteByProcessDefinitionId(processDefinitionId, deleteTransaction) { const flowNodeInstancesToRemove = await index_2.FlowNodeInstanceModel.findAll({ where: { processDefinitionId: processDefinitionId, }, transaction: deleteTransaction, attributes: ['flowNodeInstanceId'], }); await FlowNodeInstanceDatabaseAdapter_1.deleteFlowNodeInstancesInBatches(flowNodeInstancesToRemove, deleteTransaction); } static async deleteByProcessInstanceIds(processInstanceIds, deleteTransaction) { const flowNodeInstancesToRemove = await index_2.FlowNodeInstanceModel.findAll({ where: { processInstanceId: { [sequelize_1.Op.in]: processInstanceIds, }, }, transaction: deleteTransaction, attributes: ['flowNodeInstanceId'], }); await FlowNodeInstanceDatabaseAdapter_1.deleteFlowNodeInstancesInBatches(flowNodeInstancesToRemove, deleteTransaction); } static async deleteByIds(flowNodeInstanceIds, deleteTransaction) { const flowNodeInstancesToRemove = await index_2.FlowNodeInstanceModel.findAll({ where: { flowNodeInstanceId: { [sequelize_1.Op.in]: flowNodeInstanceIds, }, }, transaction: deleteTransaction, attributes: ['flowNodeInstanceId'], }); await FlowNodeInstanceDatabaseAdapter_1.deleteFlowNodeInstancesInBatches(flowNodeInstancesToRemove, deleteTransaction); } async changeProcessTokenPayload(flowNodeInstanceId, processTokenType, payload) { const encodedPayload = this.config.compressProcessTokens ? undefined : (0, processcube_engine_sdk_1.serializeJson)(payload ?? {}); const compressedPayload = this.config.compressProcessTokens ? await (0, Compression_1.compressToken)(payload ?? {}) : undefined; await asyncLocker.acquire(`${flowNodeInstanceId}-change-state`, async () => { return (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { const flowNodeInstance = await index_2.FlowNodeInstanceModel.findOne({ where: { flowNodeInstanceId: flowNodeInstanceId, }, }); switch (processTokenType) { case 'startToken': flowNodeInstance.startToken = encodedPayload; flowNodeInstance.startTokenCompressed = compressedPayload; break; case 'endToken': flowNodeInstance.endToken = encodedPayload; flowNodeInstance.endTokenCompressed = compressedPayload; break; default: break; } await flowNodeInstance.save(); }, `change process token payload for flow node instance '${flowNodeInstanceId}'`); }); } determineRequiredJoinsOnMetadataTables(bpmnTypes) { const includes = []; const bpmnTypesAsArray = this.getFlowNodeTypesRequiringMetadata(bpmnTypes); includes.push({ model: index_2.FlowNodeInstanceModel, as: 'triggeredByFlowNodeInstance', required: false, }); if (bpmnTypesAsArray.includes(processcube_engine_sdk_1.BpmnType.callActivity)) { includes.push({ model: index_2.CallActivityInstanceMetaInfoModel, as: 'callActivityInstanceMetaInfo', required: false, }); } if (bpmnTypesAsArray.includes(processcube_engine_sdk_1.BpmnType.subProcess)) { includes.push({ model: index_2.SubprocessInstancesMetaInfoModel, as: 'subprocessInstanceMetaInfo', required: false, }); } if (bpmnTypesAsArray.includes(processcube_engine_sdk_1.BpmnType.receiveTask) || bpmnTypesAsArray.includes(processcube_engine_sdk_1.BpmnType.intermediateCatchEvent) || bpmnTypesAsArray.includes(processcube_engine_sdk_1.BpmnType.boundaryEvent) || bpmnTypesAsArray.includes(processcube_engine_sdk_1.BpmnType.startEvent)) { includes.push({ model: index_2.CatchEventInstanceMetaInfoModel, as: 'catchEventInstanceMetaInfo', required: false, }); } if (bpmnTypesAsArray.includes(processcube_engine_sdk_1.BpmnType.sendTask) || bpmnTypesAsArray.includes(processcube_engine_sdk_1.BpmnType.intermediateThrowEvent) || bpmnTypesAsArray.includes(processcube_engine_sdk_1.BpmnType.endEvent)) { includes.push({ model: index_2.ThrowEventInstanceMetaInfoModel, as: 'throwEventInstanceMetaInfo', required: false, }); } if (bpmnTypesAsArray.includes(processcube_engine_sdk_1.BpmnType.manualTask)) { includes.push({ model: index_2.ManualTaskMetaInfoModel, as: 'manualTaskMetaInfo', required: false, }); } if (bpmnTypesAsArray.includes(processcube_engine_sdk_1.BpmnType.businessRuleTask) || bpmnTypesAsArray.includes(processcube_engine_sdk_1.BpmnType.serviceTask)) { includes.push({ model: index_2.ExternalTaskModel, as: 'externalTaskData', required: false, }); } if (bpmnTypesAsArray.includes(processcube_engine_sdk_1.BpmnType.serviceTask)) { includes.push({ model: index_2.HttpServiceTaskModel, as: 'httpServiceTaskData', required: false, }); } if (bpmnTypesAsArray.includes(processcube_engine_sdk_1.BpmnType.userTask)) { includes.push({ model: index_2.UserTaskMetaInfoModel, as: 'userTaskMetaInfo', required: false, }); } return includes; } getFlowNodeTypesRequiringMetadata(flowNodeTypes) { const flowNodeTypesEmpty = !flowNodeTypes || (Array.isArray(flowNodeTypes) && !flowNodeTypes.length); if (flowNodeTypesEmpty) { return Object.values(processcube_engine_sdk_1.BpmnType); } const flowNodeTypesAsArray = Array.isArray(flowNodeTypes) ? flowNodeTypes : [flowNodeTypes]; return flowNodeTypesAsArray; } async execPersistOnEnter(payload) { return this.sequelizeInstance.transaction(async (transaction) => { const matchingFlowNodeInstance = await index_2.FlowNodeInstanceModel.findOne({ where: { flowNodeInstanceId: payload.flowNodeInstanceId, }, // TODO: this is a workaround - for some reason sqlite seems to get stuck in a deadlock when using the transaction object inside a find query transaction: this.sequelizeInstance.getDialect() == 'sqlite' ? undefined : transaction, }); // Workaround for solving the problem with multiple previousFlowNodeInstanceIds for ParallelJoinGateways. if (matchingFlowNodeInstance) { matchingFlowNodeInstance.previousFlowNodeInstanceId = payload.previousFlowNodeInstanceId; await matchingFlowNodeInstance.save({ transaction: transaction }); return; } const encodedPayload = this.config.compressProcessTokens ? undefined : (0, processcube_engine_sdk_1.serializeJson)(payload.currentToken); const compressedPayload = this.config.compressProcessTokens ? await (0, Compression_1.compressToken)(payload.currentToken) : undefined; const createParams = { flowNodeInstanceId: payload.flowNodeInstanceId, flowNodeId: payload.flowNodeId, flowNodeName: payload.flowNodeName, flowNodeLane: payload.flowNodeLane, flowNodeType: `${payload.flowNodeType}`, eventType: payload.eventType ? `${payload.eventType}` : undefined, state: processcube_engine_sdk_1.FlowNodeInstanceState.running, previousFlowNodeInstanceId: payload.previousFlowNodeInstanceId, parentProcessInstanceId: payload.parentProcessInstanceId, processDefinitionId: payload.processDefinitionId, processModelId: payload.processModelId, embeddedProcessModelId: payload.embeddedProcessModelId, processInstanceId: payload.processInstanceId, correlationId: payload.correlationId, multiInstanceMetadataId: payload.multiInstanceMetadataId, ownerId: payload.ownerId, startToken: encodedPayload, startTokenCompressed: compressedPayload, triggeredByFlowNodeInstanceId: payload.triggeredByFlowNodeInstanceId, }; const createdFlowNodeInstance = await index_2.FlowNodeInstanceModel.create(createParams, { transaction: transaction }); await this.saveFlowNodeInstanceTypeSpecificData(createdFlowNodeInstance, payload.flowNodeInstanceTypeData, transaction); }); } async execBatchPersistOnEnter(payloads) { return this.sequelizeInstance.transaction(async (transaction) => { const bulkCreateParams = []; for (const payload of payloads) { const encodedPayload = this.config.compressProcessTokens ? undefined : (0, processcube_engine_sdk_1.serializeJson)(payload.currentToken); const compressedPayload = this.config.compressProcessTokens ? await (0, Compression_1.compressToken)(payload.currentToken) : undefined; const createParams = { flowNodeInstanceId: payload.flowNodeInstanceId, flowNodeId: payload.flowNodeId, flowNodeName: payload.flowNodeName, flowNodeLane: payload.flowNodeLane, flowNodeType: `${payload.flowNodeType}`, eventType: payload.eventType ? `${payload.eventType}` : undefined, state: processcube_engine_sdk_1.FlowNodeInstanceState.running, previousFlowNodeInstanceId: payload.previousFlowNodeInstanceId, parentProcessInstanceId: payload.parentProcessInstanceId, processDefinitionId: payload.processDefinitionId, processModelId: payload.processModelId, embeddedProcessModelId: payload.embeddedProcessModelId, processInstanceId: payload.processInstanceId, correlationId: payload.correlationId, multiInstanceMetadataId: payload.multiInstanceMetadataId, ownerId: payload.ownerId, startToken: encodedPayload, startTokenCompressed: compressedPayload, triggeredByFlowNodeInstanceId: payload.triggeredByFlowNodeInstanceId, }; bulkCreateParams.push(createParams); } await index_2.FlowNodeInstanceModel.bulkCreate(bulkCreateParams, { transaction: transaction }); const flowNodeInstanceTypeSpecificData = payloads .map((payload) => ({ flowNodeInstanceId: payload.flowNodeInstanceId, typeData: payload.flowNodeInstanceTypeData, })) .filter((data) => data.typeData !== undefined); if (flowNodeInstanceTypeSpecificData.length > 0) { await this.saveBatchFlowNodeInstanceTypeSpecificData(flowNodeInstanceTypeSpecificData, transaction); } }); } async persistOnStateChange(flowNodeInstanceId, newState, additionalData) { const matchingFlowNodeInstance = await index_2.FlowNodeInstanceModel.findOne({ where: { flowNodeInstanceId: flowNodeInstanceId, }, }); if (!matchingFlowNodeInstance) { throw new processcube_engine_sdk_1.NotFoundError(`FlowNodeInstance with ID \`${flowNodeInstanceId}\` not found!`); } this.ensureStateTransitionIsValid(matchingFlowNodeInstance, newState); return this.sequelizeInstance.transaction(async (transaction) => { matchingFlowNodeInstance.state = newState; if (additionalData.error !== undefined) { matchingFlowNodeInstance.error = (0, processcube_engine_sdk_1.serializeJson)(additionalData.error); } switch (newState) { case processcube_engine_sdk_1.FlowNodeInstanceState.suspended: if (additionalData.tokenPayload) { const encodedStartTokenPayload = this.config.compressProcessTokens ? undefined : (0, processcube_engine_sdk_1.serializeJson)(additionalData.tokenPayload); const compressedStartTokenPayload = this.config.compressProcessTokens ? await (0, Compression_1.compressToken)(additionalData.tokenPayload) : undefined; matchingFlowNodeInstance.startToken = encodedStartTokenPayload; matchingFlowNodeInstance.startTokenCompressed = compressedStartTokenPayload; } matchingFlowNodeInstance.suspendedAt = new Date(); break; case processcube_engine_sdk_1.FlowNodeInstanceState.finished: case processcube_engine_sdk_1.FlowNodeInstanceState.canceled: case processcube_engine_sdk_1.FlowNodeInstanceState.error: case processcube_engine_sdk_1.FlowNodeInstanceState.terminated: const endTokenPayload = additionalData.tokenPayload ?? {}; const encodedEndTokenPayload = this.config.compressProcessTokens ? undefined : (0, processcube_engine_sdk_1.serializeJson)(endTokenPayload); const compressedEndTokenPayload = this.config.compressProcessTokens ? await (0, Compression_1.compressToken)(endTokenPayload) : undefined; matchingFlowNodeInstance.endToken = encodedEndTokenPayload; matchingFlowNodeInstance.endTokenCompressed = compressedEndTokenPayload; matchingFlowNodeInstance.finishedAt = additionalData.finishedAt ?? new Date(); matchingFlowNodeInstance.triggeredByFlowNodeInstanceId = additionalData.triggeredByFlowNodeInstanceId; break; } await this.saveFlowNodeInstanceTypeSpecificData(matchingFlowNodeInstance, additionalData.typeData, transaction); if (additionalData.dataObjectValues && Object.keys(additionalData.dataObjectValues).length > 0) { for (const dataObjectId of Object.keys(additionalData.dataObjectValues)) { const valueToStore = additionalData.dataObjectValues[dataObjectId] ?? {}; const encodedValue = !this.config.compressProcessTokens ? (0, processcube_engine_sdk_1.serializeJson)(valueToStore) : undefined; const compressedValue = this.config.compressProcessTokens ? await (0, Compression_1.compressToken)(valueToStore) : undefined; const storeDataObjectRequest = { dataObjectId: dataObjectId, processDefinitionId: matchingFlowNodeInstance.processDefinitionId, processModelId: matchingFlowNodeInstance.processModelId, embeddedProcessModelId: matchingFlowNodeInstance.embeddedProcessModelId, processInstanceId: matchingFlowNodeInstance.processInstanceId, flowNodeInstanceId: flowNodeInstanceId, value: encodedValue, valueCompressed: compressedValue, }; await index_2.DataObjectModel.create(storeDataObjectRequest, { transaction: transaction }); } } await matchingFlowNodeInstance.save({ transaction: transaction }); }); } async saveDataObjectInstances(data) { const hasDataObjectsToSave = data.dataObjectValues && Object.keys(data.dataObjectValues).length > 0; if (!hasDataObjectsToSave) { return; } for (const dataObjectId of Object.keys(data.dataObjectValues)) { const valueToStore = data.dataObjectValues[dataObjectId] ?? {}; const encodedValue = !this.config.compressProcessTokens ? (0, processcube_engine_sdk_1.serializeJson)(valueToStore) : undefined; const compressedValue = this.config.compressProcessTokens ? await (0, Compression_1.compressToken)(valueToStore) : undefined; const storeDataObjectRequest = { dataObjectId: dataObjectId, processDefinitionId: data.processDefinitionId, processModelId: data.processModelId, embeddedProcessModelId: data.embeddedProcessModelId, processInstanceId: data.processInstanceId, flowNodeInstanceId: data.flowNodeInstanceId, value: encodedValue, valueCompressed: compressedValue, }; await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { return index_2.DataObjectModel.create(storeDataObjectRequest); }, `save data object with id '${dataObjectId}'`); } } ensureStateTransitionIsValid(flowNodeInstance, newState) { switch (flowNodeInstance.state) { case processcube_engine_sdk_1.FlowNodeInstanceState.canceled: case processcube_engine_sdk_1.FlowNodeInstanceState.finished: case processcube_engine_sdk_1.FlowNodeInstanceState.error: case processcube_engine_sdk_1.FlowNodeInstanceState.terminated: if (newState === processcube_engine_sdk_1.FlowNodeInstanceState.running || newState === processcube_engine_sdk_1.FlowNodeInstanceState.suspended) { throw new processcube_engine_sdk_1.InternalServerError(`Cannot change state of Flow Node Instance \`${flowNodeInstance.flowNodeInstanceId}\` to \`${newState}\`, because its current state is \`${flowNodeInstance.state}\`.`); } default: break; } } async saveFlowNodeInstanceTypeSpecificData(flowNodeInstance, data, transaction) { if (!data) { return; } switch (data.type) { case index_1.FlowNodeInstanceDataTypes.callActivity: await this.saveCallActivityData(flowNodeInstance.flowNodeInstanceId, data, transaction); break; case index_1.FlowNodeInstanceDataTypes.subprocess: await this.saveSubprocessData(flowNodeInstance.flowNodeInstanceId, data, transaction); break; case index_1.FlowNodeInstanceDataTypes.catchEvent: case index_1.FlowNodeInstanceDataTypes.receiveTask: await this.saveCatchEventData(flowNodeInstance.flowNodeInstanceId, data, transaction); break; case index_1.FlowNodeInstanceDataTypes.throwEvent: case index_1.FlowNodeInstanceDataTypes.sendTask: await this.saveThrowEventData(flowNodeInstance.flowNodeInstanceId, data, transaction); break; case index_1.FlowNodeInstanceDataTypes.manualTask: await this.saveManualTaskData(flowNodeInstance.flowNodeInstanceId, data, transaction); break; case index_1.FlowNodeInstanceDataTypes.externalServiceTask: await this.saveExternalServiceTaskData(flowNodeInstance.flowNodeInstanceId, data, transaction); break; case index_1.FlowNodeInstanceDataTypes.httpServiceTask: await this.saveHttpServiceTaskData(flowNodeInstance.flowNodeInstanceId, data, transaction); break; case index_1.FlowNodeInstanceDataTypes.userTask: await this.saveUserTaskData(flowNodeInstance.flowNodeInstanceId, data, transaction); break; default: } } async saveBatchFlowNodeInstanceTypeSpecificData(flowNodeInstances, transaction) { const typeData = flowNodeInstances[0].typeData; switch (typeData.type) { case index_1.FlowNodeInstanceDataTypes.callActivity: return await this.saveBatchCallActivityData(flowNodeInstances, transaction); case index_1.FlowNodeInstanceDataTypes.subprocess: return await this.saveBatchSubprocessData(flowNodeInstances, transaction); case index_1.FlowNodeInstanceDataTypes.catchEvent: case index_1.FlowNodeInstanceDataTypes.receiveTask: return await this.saveBatchCatchEventData(flowNodeInstances, transaction); case index_1.FlowNodeInstanceDataTypes.throwEvent: case index_1.FlowNodeInstanceDataTypes.sendTask: return await this.saveBatchThrowEventData(flowNodeInstances, transaction); case index_1.FlowNodeInstanceDataTypes.manualTask: return await this.saveBatchManualTaskData(flowNodeInstances, transaction); case index_1.FlowNodeInstanceDataTypes.externalServiceTask: return await this.saveBatchExternalServiceTaskData(flowNodeInstances, transaction); case index_1.FlowNodeInstanceDataTypes.httpServiceTask: return await this.saveBatchHttpServiceTaskData(flowNodeInstances, transaction); case index_1.FlowNodeInstanceDataTypes.userTask: return await this.saveBatchUserTaskData(flowNodeInstances, transaction); } } async saveCallActivityData(flowNodeInstanceId, data, transaction) { const flowNodeInstance = await index_2.CallActivityInstanceMetaInfoModel.findOne({ where: { flowNodeInstanceId: flowNodeInstanceId, }, transaction: this.sequelizeInstance.getDialect() == 'sqlite' ? undefined : transaction, }); if (flowNodeInstance) { flowNodeInstance.childProcessInstanceId = data.childProcessInstanceId ?? flowNodeInstance.childProcessInstanceId; flowNodeInstance.startEventId = data.startEventId ?? flowNodeInstance.startEventId; flowNodeInstance.targetProcessModelId = data.targetProcessModelId ?? flowNodeInstance.targetProcessModelId; await flowNodeInstance.save({ transaction: transaction }); } else { await index_2.CallActivityInstanceMetaInfoModel.create({ childProcessInstanceId: data.childProcessInstanceId, startEventId: data.startEventId, targetProcessModelId: data.targetProcessModelId, flowNodeInstanceId: flowNodeInstanceId, }, { transaction: transaction, }); } } async saveBatchCallActivityData(flowNodeInstances, transaction) { const callActivityData = flowNodeInstances.map((fni) => { const typeData = fni.typeData; return { childProcessInstanceId: typeData.childProcessInstanceId, startEventId: typeData.startEventId, targetProcessModelId: typeData.targetProcessModelId, flowNodeInstanceId: fni.flowNodeInstanceId, }; }); await index_2.CallActivityInstanceMetaInfoModel.bulkCreate(callActivityData, { transaction: transaction }); } async saveSubprocessData(flowNodeInstanceId, data, transaction) { const flowNodeInstance = await index_2.SubprocessInstancesMetaInfoModel.findOne({