UNPKG

@5minds/processcube_engine

Version:

The ProcessCube Engine. Stores and executes BPMNs.

952 lines • 54.2 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 ProcessInstanceDatabaseAdapter_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProcessInstanceDatabaseAdapter = void 0; const dayjs_1 = __importDefault(require("dayjs")); const inversify_1 = require("inversify"); const sequelize_1 = require("sequelize"); const processcube_engine_sdk_1 = require("@5minds/processcube_engine_sdk"); const MonitoringManager_1 = require("../MonitoringManager"); const BaseDatabaseAdapter_1 = require("./BaseDatabaseAdapter"); const CorrelationAdapter_1 = require("./CorrelationAdapter"); const FlowNodeInstanceAdapter_1 = require("./FlowNodeInstanceAdapter"); const Compression_1 = require("./Lib/Compression"); const ProcessInstanceFactory_1 = require("./Lib/ProcessInstanceFactory"); const SearchQueryBuilder_1 = require("./Lib/SearchQueryBuilder"); const SequelizeConnectionManager_1 = require("./Lib/SequelizeConnectionManager"); const Models = __importStar(require("./Models/index")); let ProcessInstanceDatabaseAdapter = ProcessInstanceDatabaseAdapter_1 = class ProcessInstanceDatabaseAdapter extends BaseDatabaseAdapter_1.DatabaseAdapter { async initialize() { await super.initialize('database_adapter:process_instances'); } async create(request) { return (0, SequelizeConnectionManager_1.executeWithRetry)(this.execCreate.bind(this, request), `create Process Instance for process model '${request.processModelId}'`); } async setMetadataValue(processInstanceId, metaDataKey, metaDataValue, flowNodeInstanceId) { const processInstance = await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => Models.ProcessInstanceModel.findOne({ where: { processInstanceId: processInstanceId, deleted: false } }), `find ProcessInstance with ID '${processInstanceId}'`); if (!processInstance) { throw new processcube_engine_sdk_1.NotFoundError(`ProcessInstance with ID \`${processInstanceId}\` not found.`); } await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { await Models.ProcessInstanceMetadataModel.create({ processInstanceId: processInstanceId, flowNodeInstanceId: flowNodeInstanceId, key: metaDataKey, value: metaDataValue, }); }, `set metadata '${metaDataKey}' for Process Instance '${processInstanceId}'`); } async getMinimalRunningProcessInstances() { const resumableProcessInstances = await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { return Models.ProcessInstanceModel.findAll({ attributes: ['processInstanceId', 'state', 'parentProcessInstanceId'], where: { state: processcube_engine_sdk_1.ProcessInstanceState.running, deleted: false, }, }); }, 'get a trimmed down set of all running Process Instances'); return resumableProcessInstances.map((processInstance) => { return { processInstanceId: processInstance.processInstanceId, state: processInstance.state, parentProcessInstanceId: processInstance.parentProcessInstanceId, }; }); } async getProcessInstances(processInstanceIds) { const whereClause = { processInstanceId: { [sequelize_1.Op.in]: processInstanceIds, }, deleted: false, }; return this.getUnorderedProcessInstanceList(whereClause); } async getDirectChildProcessInstanceIds(parentProcessInstanceId, parentFlowNodeType, authorizationQuery) { let whereClause = { parentProcessInstanceId: parentProcessInstanceId, deleted: false, }; const include = []; if (parentFlowNodeType === processcube_engine_sdk_1.BpmnType.subProcess) { whereClause['embeddedProcessModelId'] = { [sequelize_1.Op.not]: null, }; } else if (parentFlowNodeType === processcube_engine_sdk_1.BpmnType.callActivity) { whereClause['embeddedProcessModelId'] = { [sequelize_1.Op.eq]: null, }; } if (authorizationQuery) { whereClause[sequelize_1.Op.or] = this.buildAuthorizationFilters(authorizationQuery.allowedLanes, authorizationQuery.requestingUserId); include.push({ model: Models.FlowNodeInstanceModel.scope(null), as: 'flowNodeInstances', attributes: [], required: false, }); } const result = await this.getProcessInstanceIdsList(whereClause, include); return result.map((pi) => pi.processInstanceId); } async getAllDescendantProcessInstanceIds(parentProcessInstanceId, parentFlowNodeType, authorizationQuery) { const processInstance = await this.getProcessInstances([parentProcessInstanceId]); if (processInstance.length === 0) { return []; } const whereClause = { correlationId: processInstance[0].correlationId, createdAt: { [sequelize_1.Op.gte]: processInstance[0].createdAt, }, deleted: false, }; const include = []; if (parentFlowNodeType === processcube_engine_sdk_1.BpmnType.subProcess) { whereClause['embeddedProcessModelId'] = { [sequelize_1.Op.not]: null, }; } else if (parentFlowNodeType === processcube_engine_sdk_1.BpmnType.callActivity) { whereClause['embeddedProcessModelId'] = { [sequelize_1.Op.eq]: null, }; } if (authorizationQuery) { whereClause[sequelize_1.Op.or] = this.buildAuthorizationFilters(authorizationQuery.allowedLanes, authorizationQuery.requestingUserId); include.push({ model: Models.FlowNodeInstanceModel.scope(null), as: 'flowNodeInstances', attributes: [], required: false, }); } let potentialDescendants = await this.getProcessInstanceIdsList(whereClause, include); const childProcessInstanceIds = []; let resultLength = null; while (resultLength !== childProcessInstanceIds.length) { resultLength = childProcessInstanceIds.length; potentialDescendants = potentialDescendants.filter((pi) => { if (pi.parentProcessInstanceId === parentProcessInstanceId || childProcessInstanceIds.includes(pi.parentProcessInstanceId)) { childProcessInstanceIds.push(pi.processInstanceId); return false; } return true; }); } return childProcessInstanceIds; } async getAncestorProcessInstanceIds(processInstanceId, onlyForEmbeddedProcesses, authorizationQuery) { const processInstanceQuery = { processInstanceId: processInstanceId, }; if (authorizationQuery) { processInstanceQuery.allowedLanes = authorizationQuery.allowedLanes; processInstanceQuery.ownerId = authorizationQuery.requestingUserId; } const queryResult = await this.query(processInstanceQuery); if (queryResult.processInstances.length === 0 || !queryResult.processInstances[0].parentProcessInstanceId || (onlyForEmbeddedProcesses && !queryResult.processInstances[0].embeddedProcessModelId)) { return []; } const whereClause = { correlationId: queryResult.processInstances[0].correlationId, createdAt: { [sequelize_1.Op.lte]: queryResult.processInstances[0].createdAt, }, deleted: false, }; const include = []; if (onlyForEmbeddedProcesses) { whereClause['embeddedProcessModelId'] = { [sequelize_1.Op.not]: null, }; } if (authorizationQuery) { whereClause[sequelize_1.Op.or] = this.buildAuthorizationFilters(authorizationQuery.allowedLanes, authorizationQuery.requestingUserId); include.push({ model: Models.FlowNodeInstanceModel.scope(null), as: 'flowNodeInstances', attributes: [], required: false, }); } let potentialAncestors = await this.getProcessInstanceIdsList(whereClause, include); let resultLength = 0; const result = [queryResult.processInstances[0].parentProcessInstanceId]; while (resultLength !== result.length) { resultLength = result.length; const ancestor = potentialAncestors.find((pi) => pi.processInstanceId === result[result.length - 1]); if (ancestor && ancestor.parentProcessInstanceId) { result.push(ancestor.parentProcessInstanceId); } } return result.toReversed(); } async getProcessInstancesByProcessDefinitionIdAndState(processDefinitionId, state) { const whereClause = { processDefinitionId: processDefinitionId, state: state, deleted: false, }; return this.getUnorderedProcessInstanceList(whereClause); } async countRunningInstancesForModel(processModelId) { return await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { return Models.ProcessInstanceModel.count({ where: { processModelId: processModelId, state: processcube_engine_sdk_1.ProcessInstanceState.running, deleted: false, }, }); }, `Count running instances for process model ${processModelId}`); } async getRetryableChildProcessInstances(parentProcessInstanceIds) { const whereClause = { parentProcessInstanceId: { [sequelize_1.Op.in]: parentProcessInstanceIds, }, state: { [sequelize_1.Op.or]: [processcube_engine_sdk_1.ProcessInstanceState.error, processcube_engine_sdk_1.ProcessInstanceState.terminated], }, deleted: false, }; return this.getUnorderedProcessInstanceList(whereClause); } async query(query, offset = 0, limit = 0, sort, includeXml = true, includeDeleted = false, includeCount = false) { const { count, processInstanceIds } = await this.getProcessInstanceIdsToLoad(query, offset, limit, sort, includeDeleted, includeCount); const functionReporter = MonitoringManager_1.MonitoringManager.getNewFunctionReporter('ProcessInstanceAdapter.query', { queryKeys: Object.keys(query) ?? [], offset, limit, includeXml, includeDeleted, includeCount, }, { queryValues: Object.values(query) ?? [], processInstanceIdsCount: processInstanceIds.length }); const whereQueryForProcessInstanceIds = { processInstanceId: { [sequelize_1.Op.in]: processInstanceIds, }, }; const request = { where: whereQueryForProcessInstanceIds, include: [ { model: Models.CorrelationModel, as: 'correlation', attributes: ['correlationId'], required: false, include: [ { model: Models.CorrelationMetadataModel, as: 'correlationMetadata', attributes: ['key', 'value', 'createdAt'], required: false, }, ], }, { model: Models.ProcessInstanceMetadataModel, as: 'processInstanceMetadata', attributes: ['key', 'value', 'createdAt'], required: false, }, { model: Models.FlowNodeInstanceModel, as: 'triggeredByFlowNodeInstance', required: false, }, ], order: this.buildQueryOrderSettings(sort), }; if (includeXml) { request.include.push({ model: Models.ProcessDefinitionModel, attributes: ['xml'], required: false, }); } const results = await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => Models.ProcessInstanceModel.findAll(request), 'query Process Instances'); const processInstances = []; for (const rawProcessInstance of results) { const processInstance = await (0, ProcessInstanceFactory_1.createProcessInstanceFromModel)(rawProcessInstance); processInstances.push(processInstance); } functionReporter.finish(); return { processInstances: processInstances, totalCount: count, }; } async transferOwnership(processInstanceIds, newOwner) { const transaction = await this.sequelizeInstance.transaction(); try { await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => Models.ProcessInstanceModel.update({ ownerId: newOwner.userId, ownerToken: newOwner.token, }, { where: { processInstanceId: { [sequelize_1.Op.in]: processInstanceIds }, deleted: false, }, transaction: transaction, }), `transfer ownership of Process Instances '${processInstanceIds.join(', ')}' to new owner ${newOwner.userId}`); await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => Models.FlowNodeInstanceModel.update({ ownerId: newOwner.userId, }, { where: { processInstanceId: { [sequelize_1.Op.in]: processInstanceIds }, }, transaction: transaction, }), `transfer ownership of Flow Node Instances belonging to Process Instances '${processInstanceIds.join(', ')}' to new owner ${newOwner.userId}`); await transaction.commit(); } catch (error) { await transaction.rollback(); throw error; } } async finish(processInstanceId, endEventData, includeFlowNodeInstances) { const processInstance = await this.findProcessInstanceAndVerifyRunningState(processInstanceId); const endTokenToEncode = endEventData?.endEventToken ?? {}; const encodedEndToken = this.config.compressProcessTokens ? undefined : JSON.stringify(endTokenToEncode); const compressedEndToken = this.config.compressProcessTokens ? await (0, Compression_1.compressToken)(endTokenToEncode) : undefined; processInstance.state = processcube_engine_sdk_1.ProcessInstanceState.finished; processInstance.error = null; // This is important for retried Process Instances, which will have an error attached to them. processInstance.endEventId = endEventData?.endEventId; processInstance.endEventType = endEventData?.endEventType; processInstance.endToken = encodedEndToken; processInstance.endTokenCompressed = compressedEndToken; this.calculateTotalRuntime(processInstance); return (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { const transaction = await this.sequelizeInstance.transaction(); try { await processInstance.save({ transaction }); if (includeFlowNodeInstances) { await this.cleanupFlowNodeInstances(processInstanceId, transaction); } await transaction.commit(); } catch (error) { await transaction.rollback(); } }, `finish Process Instance '${processInstanceId}'`); } async finishWithError(processInstanceId, error, endEventData, includeFlowNodeInstances) { const processInstance = await this.findProcessInstanceAndVerifyRunningState(processInstanceId); const endTokenToEncode = endEventData?.endEventToken ?? {}; const encodedEndToken = this.config.compressProcessTokens ? undefined : JSON.stringify(endTokenToEncode); const compressedEndToken = this.config.compressProcessTokens ? await (0, Compression_1.compressToken)(endTokenToEncode) : undefined; processInstance.state = processcube_engine_sdk_1.ProcessInstanceState.error; processInstance.error = (0, processcube_engine_sdk_1.serializeJson)(error); processInstance.endEventId = endEventData?.endEventId; processInstance.endEventType = endEventData?.endEventType; processInstance.endToken = encodedEndToken; processInstance.endTokenCompressed = compressedEndToken; this.calculateTotalRuntime(processInstance); return (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { const transaction = await this.sequelizeInstance.transaction(); try { await processInstance.save({ transaction }); if (includeFlowNodeInstances) { await this.cleanupFlowNodeInstances(processInstanceId, transaction, processcube_engine_sdk_1.FlowNodeInstanceState.terminated, (0, processcube_engine_sdk_1.serializeJson)(error)); } await transaction.commit(); } catch (error) { await transaction.rollback(); } }, `finish Process Instance '${processInstanceId}' with error`); } async terminate(processInstanceId, error, endEventData, includeFlowNodeInstances) { const processInstance = await this.findProcessInstanceAndVerifyRunningState(processInstanceId); const endTokenToEncode = endEventData?.endEventToken ?? {}; const encodedEndToken = this.config.compressProcessTokens ? undefined : JSON.stringify(endTokenToEncode); const compressedEndToken = this.config.compressProcessTokens ? await (0, Compression_1.compressToken)(endTokenToEncode) : undefined; processInstance.state = processcube_engine_sdk_1.ProcessInstanceState.terminated; processInstance.error = (0, processcube_engine_sdk_1.serializeJson)(error); processInstance.terminatedByUserId = error?.additionalInformation?.killedBy?.userId; processInstance.endEventId = endEventData?.endEventId; processInstance.endEventType = endEventData?.endEventType; processInstance.endToken = encodedEndToken; processInstance.endTokenCompressed = compressedEndToken; this.calculateTotalRuntime(processInstance); return (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { const transaction = await this.sequelizeInstance.transaction(); try { await processInstance.save({ transaction }); if (includeFlowNodeInstances) { await this.cleanupFlowNodeInstances(processInstanceId, transaction, processcube_engine_sdk_1.FlowNodeInstanceState.terminated, (0, processcube_engine_sdk_1.serializeJson)(error)); } await transaction.commit(); } catch (error) { await transaction.rollback(); } }, `terminate Process Instance '${processInstanceId}'`); } async changeProcessDefinition(processInstanceId, newProcessModelHash) { return (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { await Models.ProcessInstanceModel.update({ processModelHash: newProcessModelHash }, { where: { processInstanceId, }, }); }, `change Process Model Hash of Process Instance '${processInstanceId}' to '${newProcessModelHash}'`); } async reset(processInstanceId, flowNodeInstanceIdsToReset, flowNodeInstanceIdsToDelete, processInstanceIdsToDelete) { const queryParams = { where: { processInstanceId: processInstanceId, deleted: false, }, }; const processInstance = await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => Models.ProcessInstanceModel.findOne(queryParams), `find process instance '${processInstanceId}'`); if (!processInstance) { throw new processcube_engine_sdk_1.NotFoundError(`ProcessInstance with ID \`${processInstanceId}\` not found.`); } if (processInstance.state !== processcube_engine_sdk_1.ProcessInstanceState.error && processInstance.state !== processcube_engine_sdk_1.ProcessInstanceState.terminated) { throw new processcube_engine_sdk_1.BadRequestError(`ProcessInstance with ID \`${processInstanceId}\` is not in an error or terminated state.`); } const transaction = await this.sequelizeInstance.transaction(); try { processInstance.state = processcube_engine_sdk_1.ProcessInstanceState.running; processInstance.restartedAt = (0, dayjs_1.default)().toDate(); processInstance.finishedAt = null; processInstance.error = null; await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { await processInstance.save({ transaction: transaction }); }, `reset process instance '${processInstanceId}'`); if (flowNodeInstanceIdsToDelete?.length > 0) { await FlowNodeInstanceAdapter_1.FlowNodeInstanceDatabaseAdapter.deleteByIds(flowNodeInstanceIdsToDelete, transaction); await this.deleteMetadataValueByFlowNodeInstanceIds(flowNodeInstanceIdsToDelete, transaction); await CorrelationAdapter_1.CorrelationDatabaseAdapter.deleteMetadataValueByFlowNodeInstanceIds(flowNodeInstanceIdsToDelete, transaction); } for (const flowNodeInstance of flowNodeInstanceIdsToReset) { if (typeof flowNodeInstance === 'string') { await FlowNodeInstanceAdapter_1.FlowNodeInstanceDatabaseAdapter.reset(flowNodeInstance, transaction); } else { await FlowNodeInstanceAdapter_1.FlowNodeInstanceDatabaseAdapter.reset(flowNodeInstance.flowNodeInstanceId, transaction, flowNodeInstance.isUnstartedSubProcess); } } if (processInstanceIdsToDelete?.length > 0) { await ProcessInstanceDatabaseAdapter_1.deleteByIds(processInstanceIdsToDelete, transaction); } await transaction.commit(); } catch (error) { await transaction.rollback(); throw error; } } async flagAsDeletedByIds(processInstanceIds) { return (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { await Models.ProcessInstanceModel.update({ deleted: true }, { where: { processInstanceId: { [sequelize_1.Op.in]: processInstanceIds, }, }, }); }, `set deleted flag for process instances with IDs '${processInstanceIds.join(';')}'`); } async deleteByIds(processInstanceIds) { return (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { const deleteTransaction = await this.sequelizeInstance.transaction(); try { const deleteOptions = { where: { processInstanceId: { [sequelize_1.Op.in]: processInstanceIds, }, }, transaction: deleteTransaction, }; await Models.ProcessInstanceModel.destroy(deleteOptions); await Models.ProcessInstanceMetadataModel.destroy(deleteOptions); await FlowNodeInstanceAdapter_1.FlowNodeInstanceDatabaseAdapter.deleteByProcessInstanceIds(processInstanceIds, deleteTransaction); await ProcessInstanceDatabaseAdapter_1.deleteEmptyCorrelations(deleteTransaction); await deleteTransaction.commit(); } catch (error) { await deleteTransaction.rollback(); } }, `delete Process Instance with id '${processInstanceIds}'`); } static async deleteByIds(processInstanceIds, transaction) { const deleteOptions = { where: { processInstanceId: { [sequelize_1.Op.in]: processInstanceIds, }, }, transaction: transaction, }; await Models.ProcessInstanceModel.destroy(deleteOptions); await Models.ProcessInstanceMetadataModel.destroy(deleteOptions); await FlowNodeInstanceAdapter_1.FlowNodeInstanceDatabaseAdapter.deleteByProcessInstanceIds(processInstanceIds, transaction); await ProcessInstanceDatabaseAdapter_1.deleteEmptyCorrelations(transaction); } async deleteByProcessDefinitionId(processDefinitionId) { return (0, SequelizeConnectionManager_1.executeWithRetry)(async () => { const deleteTransaction = await this.sequelizeInstance.transaction(); try { await ProcessInstanceDatabaseAdapter_1.deleteByProcessDefinitionId(processDefinitionId, deleteTransaction); await deleteTransaction.commit(); } catch (error) { await deleteTransaction.rollback(); } }, `delete Process Instances by process definition id '${processDefinitionId}'`); } static async deleteByProcessDefinitionId(processDefinitionId, deleteTransaction) { const processInstanceIdsToRemove = (await Models.ProcessInstanceModel.findAll({ where: { processDefinitionId: processDefinitionId, }, attributes: ['processInstanceId'], transaction: deleteTransaction, })).map((processInstance) => processInstance.processInstanceId); const deleteChunkSize = 1000; while (processInstanceIdsToRemove.length > 0) { const processInstanceIdChunk = processInstanceIdsToRemove.splice(0, deleteChunkSize); const deleteChunkOptions = { where: { processInstanceId: { [sequelize_1.Op.in]: processInstanceIdChunk, }, }, transaction: deleteTransaction, }; await Models.ProcessInstanceModel.destroy(deleteChunkOptions); await Models.ProcessInstanceMetadataModel.destroy(deleteChunkOptions); } await ProcessInstanceDatabaseAdapter_1.deleteEmptyCorrelations(deleteTransaction); } async execCreate(request) { this.validateCreateRequest(request); const existingProcessInstance = await Models.ProcessInstanceModel.findOne({ where: { processInstanceId: request.processInstanceId, }, }); if (existingProcessInstance && existingProcessInstance.deleted === false) { return; } if (existingProcessInstance && existingProcessInstance.deleted === true) { await this.deleteByIds([existingProcessInstance.processInstanceId]); } await this.storeCorrelation(request); const startTokenToEncode = request.startToken ?? {}; const encodedStartToken = this.config.compressProcessTokens ? undefined : JSON.stringify(startTokenToEncode); const compressedStartToken = this.config.compressProcessTokens ? await (0, Compression_1.compressToken)(startTokenToEncode) : undefined; const createProcessInstanceParams = { correlationId: request.correlationId, processInstanceId: request.processInstanceId, processDefinitionId: request.processDefinitionId, processModelId: request.processModelId, processModelName: request.processModelName, processModelVersion: request.processModelVersion, parentProcessInstanceId: request.parentProcessInstanceId, processModelHash: request.processModelHash, ownerId: request.identity.userId, ownerToken: request.identity.token, state: processcube_engine_sdk_1.ProcessInstanceState.running, startEventId: request.startEventId, startEventType: request.startEventType, startToken: encodedStartToken, startTokenCompressed: compressedStartToken, startedByRootAccessToken: request.startedByRootAccessToken, triggeredByFlowNodeInstanceId: request.triggeredByFlowNodeInstanceId, embeddedProcessModelId: request.embeddedProcessModelId, }; await Models.ProcessInstanceModel.create(createProcessInstanceParams); } async getProcessInstanceIdsList(whereClause, include) { const request = { where: whereClause, include: include, attributes: ['processInstanceId', 'parentProcessInstanceId'], }; const result = await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => Models.ProcessInstanceModel.findAll(request), 'get Process Instances ids'); return result; } async getUnorderedProcessInstanceList(whereClause) { const request = { where: whereClause, include: [ { model: Models.CorrelationModel, as: 'correlation', attributes: ['correlationId'], required: false, include: [ { model: Models.CorrelationMetadataModel, as: 'correlationMetadata', attributes: ['key', 'value', 'createdAt'], required: false, }, ], }, { model: Models.ProcessInstanceMetadataModel, as: 'processInstanceMetadata', attributes: ['key', 'value', 'createdAt'], required: false, }, { model: Models.ProcessDefinitionModel, attributes: ['xml'], required: false, }, ], }; const results = await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => Models.ProcessInstanceModel.findAll(request), 'get Process Instances by id'); const processInstances = []; for (const rawProcessInstance of results) { const processInstance = await (0, ProcessInstanceFactory_1.createProcessInstanceFromModel)(rawProcessInstance); processInstances.push(processInstance); } return processInstances; } async getProcessInstanceIdsToLoad(query, offset, limit, sortSettings, includeDeleted = false, includeCount = false) { const sortByCorrelationMetadata = sortSettings?.sortBy === processcube_engine_sdk_1.ProcessInstanceSortableColumns.correlationMetadata; const sortByProcessInstanceMetadata = sortSettings?.sortBy === processcube_engine_sdk_1.ProcessInstanceSortableColumns.processInstanceMetadata; const sortByStartToken = sortSettings?.sortBy === processcube_engine_sdk_1.ProcessInstanceSortableColumns.startToken; const sortByEndToken = sortSettings?.sortBy === processcube_engine_sdk_1.ProcessInstanceSortableColumns.endToken; // MySQL requires this column to be present in the "GroupBy" clause. const getOrderByColumnNameFromOrderSettings = () => { if (!sortSettings?.sortBy) { return ['ProcessInstance.createdAt']; } if (sortByStartToken) { this.logger.warn('Sorting by start token is no longer supported. Using default sorting.'); return ['ProcessInstance.createdAt']; } if (sortByEndToken) { this.logger.warn('Sorting by end token is no longer supported. Using default sorting.'); return ['ProcessInstance.createdAt']; } if (sortByCorrelationMetadata) { return ['correlation.correlationMetadata.key', 'correlation.correlationMetadata.value']; } if (sortByProcessInstanceMetadata) { return ['processInstanceMetadata.key', 'processInstanceMetadata.value']; } return [`ProcessInstance.${sortSettings.sortBy}`]; }; const orderByColumNames = getOrderByColumnNameFromOrderSettings(); const includes = this.getIncludesForPlainQueries(query, sortSettings); const requiresGrouping = Object.keys(includes).length > 0; const where = await this.buildProcessInstanceWhereClause(query, includeDeleted); const options = { where: where, attributes: ['processInstanceId', ...orderByColumNames], include: Object.values(includes), ...(0, SearchQueryBuilder_1.buildPagination)(offset, limit), order: this.buildQueryOrderSettings(sortSettings), subQuery: false, group: requiresGrouping ? ['ProcessInstance.processInstanceId', ...orderByColumNames, 'ProcessInstance.id'] : undefined, }; if (!includeCount) { return this.getProcessInstancesWithoutCount(options); } return this.getProcessInstancesWithCount(options); } getIncludesForPlainQueries(query, sortSettings) { const sortByCorrelationMetadata = sortSettings?.sortBy === processcube_engine_sdk_1.ProcessInstanceSortableColumns.correlationMetadata; const sortByProcessInstanceMetadata = sortSettings?.sortBy === processcube_engine_sdk_1.ProcessInstanceSortableColumns.processInstanceMetadata; const includes = {}; const includeCorrelationMetadata = sortByCorrelationMetadata || query.correlationMetadata != undefined; if (includeCorrelationMetadata) { includes[Models.CorrelationModel.name] = { model: Models.CorrelationModel.scope(null), attributes: [], required: false, include: [ { model: Models.CorrelationMetadataModel.scope(null), attributes: [], required: false, }, ], }; } const includeProcessInstanceMetadata = sortByProcessInstanceMetadata || query.processInstanceMetadata != undefined; if (includeProcessInstanceMetadata) { includes[Models.CorrelationMetadataModel.name] = { model: Models.ProcessInstanceMetadataModel.scope(null), attributes: [], required: false, }; } const includeFlowNodeInstances = query.allowedLanes != undefined || query.requestingUserId != undefined; if (includeFlowNodeInstances) { includes[Models.FlowNodeInstanceModel.name] = { model: Models.FlowNodeInstanceModel.scope(null), as: 'flowNodeInstances', attributes: [], required: false, }; } return includes; } async buildProcessInstanceWhereClause(query, includeDeleted = false) { const whereClause = {}; if (!includeDeleted) { whereClause.deleted = false; } whereClause.correlationId = (0, SearchQueryBuilder_1.buildSearchQueryFilter)(query.correlationId, 'ProcessInstance.correlationId', this.sequelizeInstance); whereClause.processInstanceId = (0, SearchQueryBuilder_1.buildSearchQueryFilter)(query.processInstanceId, 'ProcessInstance.processInstanceId', this.sequelizeInstance, true, true); whereClause.processDefinitionId = (0, SearchQueryBuilder_1.buildSearchQueryFilter)(query.processDefinitionId, 'ProcessInstance.processDefinitionId', this.sequelizeInstance); whereClause.processModelId = (0, SearchQueryBuilder_1.buildSearchQueryFilter)(query.processModelId, 'ProcessInstance.processModelId', this.sequelizeInstance); whereClause.processModelName = (0, SearchQueryBuilder_1.buildSearchQueryFilter)(query.processModelName, 'ProcessInstance.processModelName', this.sequelizeInstance); whereClause.processModelVersion = (0, SearchQueryBuilder_1.buildSearchQueryFilter)(query.processModelVersion, 'ProcessInstance.processModelVersion', this.sequelizeInstance); whereClause.processModelHash = (0, SearchQueryBuilder_1.buildSearchQueryFilter)(query.processModelHash, 'ProcessInstance.processModelHash', this.sequelizeInstance, true, true); whereClause.embeddedProcessModelId = (0, SearchQueryBuilder_1.buildSearchQueryFilter)(query.embeddedProcessModelId, 'ProcessInstance.embeddedProcessModelId', this.sequelizeInstance); whereClause.state = (0, SearchQueryBuilder_1.buildSearchQueryFilter)(query.state, 'ProcessInstance.state', this.sequelizeInstance, true, true); whereClause.ownerId = (0, SearchQueryBuilder_1.buildSearchQueryFilter)(query.ownerId, 'ProcessInstance.ownerId', this.sequelizeInstance); whereClause.startedByRootAccessToken = query.startedByRootAccessToken; whereClause.parentProcessInstanceId = (0, SearchQueryBuilder_1.buildSearchQueryFilter)(query.parentProcessInstanceId, 'ProcessInstance.parentProcessInstanceId', this.sequelizeInstance, true, true); whereClause.terminatedByUserId = (0, SearchQueryBuilder_1.buildSearchQueryFilter)(query.terminatedByUserId, 'ProcessInstance.terminatedByUserId', this.sequelizeInstance); whereClause.triggeredByFlowNodeInstanceId = (0, SearchQueryBuilder_1.buildSearchQueryFilter)(query.triggeredByFlowNodeInstance, 'ProcessInstance.triggeredByFlowNodeInstanceId', this.sequelizeInstance, true, true); whereClause.startEventId = (0, SearchQueryBuilder_1.buildSearchQueryFilter)(query.startEventId, 'ProcessInstance.startEventId', this.sequelizeInstance); whereClause.startEventType = this.buildEventTypeFilter(query, 'ProcessInstance.startEventType'); if (query.startToken) { this.logger.warn('The "startToken" Filter is no longer supported, because compressed tokens cannot be filtered.'); } if (query.endToken) { this.logger.warn('The "endToken" Filter is no longer supported, because compressed tokens cannot be filtered.'); } whereClause.endEventType = this.buildEventTypeFilter(query, 'ProcessInstance.endEventType'); whereClause.endEventId = (0, SearchQueryBuilder_1.buildSearchQueryFilter)(query.endEventId, 'ProcessInstance.endEventId', this.sequelizeInstance); whereClause.finishedAt = (0, SearchQueryBuilder_1.buildDateFilter)(query.finishedAt, query.finishedAfter, query.finishedBefore); whereClause.createdAt = (0, SearchQueryBuilder_1.buildDateFilter)(query.createdAt, query.createdAfter, query.createdBefore); whereClause.updatedAt = (0, SearchQueryBuilder_1.buildDateFilter)(query.updatedAt, query.updatedAfter, query.updatedBefore); whereClause[sequelize_1.Op.and] = []; if (query.durationInMillisecondsLessThan) { whereClause[sequelize_1.Op.and].push({ durationInMilliseconds: { [sequelize_1.Op.lt]: query.durationInMillisecondsLessThan }, }); } if (query.durationInMillisecondsGreaterThan) { whereClause[sequelize_1.Op.and].push({ durationInMilliseconds: { [sequelize_1.Op.gte]: query.durationInMillisecondsGreaterThan }, }); } const authorizationFilters = this.buildAuthorizationFilters(query.allowedLanes, query.requestingUserId); if (authorizationFilters) { whereClause[sequelize_1.Op.and].push({ [sequelize_1.Op.or]: authorizationFilters, }); } const searchQuery = Reflect.ownKeys(whereClause) .filter((key) => whereClause[key] !== undefined) .reduce((previousKeys, currentKey) => ({ ...previousKeys, [currentKey]: whereClause[currentKey] }), {}); return searchQuery; } async getProcessInstancesWithCount(options) { const findAndCountAllResult = await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => Models.ProcessInstanceModel.findAndCountAll(options), 'find and count Process Instances to query'); const rowCount = Array.isArray(findAndCountAllResult.count) ? findAndCountAllResult.count.length : findAndCountAllResult.count; const processInstanceIds = findAndCountAllResult.rows.map((result) => result.processInstanceId); return { count: rowCount, processInstanceIds: processInstanceIds, }; } async getProcessInstancesWithoutCount(options) { const findAllResult = await (0, SequelizeConnectionManager_1.executeWithRetry)(async () => Models.ProcessInstanceModel.findAll(options), 'find Process Instance Ids to query without count'); const processInstanceIds = findAllResult.map((result) => result.processInstanceId); return { count: undefined, processInstanceIds: processInstanceIds, }; } buildAuthorizationFilters(allowedLanes, requestingUserId) { if (!allowedLanes && !requestingUserId) { return undefined; } const query = []; if (allowedLanes) { query.push({ [sequelize_1.Op.or]: [ { '$flowNodeInstances.flowNodeLane$': { [sequelize_1.Op.in]: allowedLanes } }, { [sequelize_1.Op.and]: [{ '$flowNodeInstances.flowNodeLane$': { [sequelize_1.Op.eq]: null } }, { '$flowNodeInstances.id$': { [sequelize_1.Op.ne]: null } }], }, ], }); } if (requestingUserId) { query.push({ ownerId: requestingUserId, }); } return query; } buildEventTypeFilter(query, eventTypeProperty) { const eventTypeName = eventTypeProperty.replace('ProcessInstance.', ''); if (query[eventTypeName] === undefined) { return undefined; } const eventTypeQuery = query[eventTypeName]; // A null value denotes a regular start event. const isNullFilter = eventTypeQuery === null || (Array.isArray(eventTypeQuery) && eventTypeQuery.every((entry) => entry === null)); if (isNullFilter) { return { [sequelize_1.Op.is]: null }; } if (!Array.isArray(eventTypeQuery) || eventTypeQuery.length === 1) { return (0, SearchQueryBuilder_1.buildSearchQueryFilter)(eventTypeQuery, eventTypeProperty, this.sequelizeInstance); } const filters = []; const hasNullFilter = eventTypeQuery.some((entry) => entry == null); if (hasNullFilter) { filters.push({ [sequelize_1.Op.is]: null }); } const filterWithoutNullValues = eventTypeQuery.filter((entry) => entry != null); if (filterWithoutNullValues.length > 0) { filters.push({ [sequelize_1.Op.in]: filterWithoutNullValues }); } return { [sequelize_1.Op.or]: filters, }; } async storeCorrelation(request) { const existingCorrelation = await Models.CorrelationModel.findOne({ where: { correlationId: request.correlationId, }, }); if (!existingCorrelation) { try { await Models.CorrelationModel.create({ correlationId: request.correlationId }); } catch (error) { if (error.name === 'SequelizeUniqueConstraintError') { // This happens if the correlation was already stored while this check is running. Can be ignored. // Note that we must not use transactions here, because if the INSERT command encounters an error, it breaks the entire transaction on DB side. return; } throw error; } } } static async deleteEmptyCorrelations(transaction) { const emptyCorrelationsToRemove = (await Models.CorrelationModel.findAll({ attributes: ['correlationId'], include: [ { model: Models.ProcessInstanceModel, as: 'processInstances', attributes: ['correlationId'], required: false, }, ], where: sequelize_1.Sequelize.where(sequelize_1.Sequelize.col('processInstances.correlationId'), 'IS', null), transaction: transaction, })).map((correlation) => correlation.correlationId); if (emptyCorrelationsToRemove.length > 0) { await Models.CorrelationMetadataModel.destroy({ where: { correlationId: { [sequelize_1.Op.in]: emptyCorrelationsToRemove, }, }, transaction: transaction, }); await Models.CorrelationModel.destroy({ where: { correlationId: { [sequelize_1.Op.in]: emptyCorrelationsToRemove, }, }, transaction: transaction, }); } } async deleteMetadataValueByFlowNodeInstanceIds(flowNodeInstanceIds, transaction) { await Models.ProcessInstanceMetadataModel.destroy({ where: { flowNodeInstanceId: { [sequelize_1.Op.in]: flowNodeInstanceIds, }, }, transaction: transaction, }); } validateCreateRequest(request) { if (!request.correlationId) { throw new processcube_engine_sdk_1.BadRequestError('Cannot create ProcessInstance: `correlationId` is missing!'); } if (!request.processInstanceId) { throw new processcube_engine_sdk_1.BadRequestError('Cannot create ProcessInstance: `processInstanceId` is missing!'); } if (!request.processDefinitionId) { throw new processcube_engine_sdk_1.BadRequestError('Cannot create ProcessInstance: `processDefinitionId` is missing!'); } if (!request.processModelId) { throw new processcube_engine_sdk_1.BadRequestError('Cannot create ProcessInstance: `processModelId` is missing!'); } if (!request.processModelHash) { throw new processcube_engine_sdk_1.BadRequestError('Cannot create ProcessInstance: `processModelHash` is missing!'); } if (!request.startEventId) { throw new processcube_engine_sdk_1.BadRequestError('Cannot create ProcessInstance: `startEventId` is missing!'); } } async findProcessInstanceAndVerifyRunningState(processInstanceId) { const queryParams = { where: { processInstanceId: processInstanceId, deleted: false, }, }; const processInstance = a