@5minds/processcube_engine
Version:
The ProcessCube Engine. Stores and executes BPMNs.
892 lines (891 loc) • 67 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __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({